]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
Move towards unnest (faster than explode_array); bring upgrade script up to speed
[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 ALTER TABLE permission.grp_perm_map        DROP CONSTRAINT grp_perm_map_perm_fkey;
15 ALTER TABLE permission.usr_perm_map        DROP CONSTRAINT usr_perm_map_perm_fkey;
16 ALTER TABLE permission.usr_object_perm_map DROP CONSTRAINT usr_object_perm_map_perm_fkey;
17 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_or_record_once_per_owner;
18 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_once_per_owner;
19
20 \qecho Beginning the transaction now
21
22 BEGIN;
23
24 -- Highest-numbered individual upgrade script incorporated herein:
25
26 INSERT INTO config.upgrade_log (version) VALUES ('0449');
27
28 -- Remove some uses of the connectby() function from the tablefunc contrib module
29 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT, INT ) RETURNS SETOF actor.org_unit AS $$
30     WITH RECURSIVE descendant_depth AS (
31         SELECT  ou.id,
32                 ou.parent_ou,
33                 out.depth
34           FROM  actor.org_unit ou
35                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
36                 JOIN anscestor_depth ad ON (ad.id = ou.id)
37           WHERE ad.depth = $2
38             UNION ALL
39         SELECT  ou.id,
40                 ou.parent_ou,
41                 out.depth
42           FROM  actor.org_unit ou
43                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
44                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
45     ), anscestor_depth AS (
46         SELECT  ou.id,
47                 ou.parent_ou,
48                 out.depth
49           FROM  actor.org_unit ou
50                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
51           WHERE ou.id = $1
52             UNION ALL
53         SELECT  ou.id,
54                 ou.parent_ou,
55                 out.depth
56           FROM  actor.org_unit ou
57                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
58                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
59     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
60 $$ LANGUAGE SQL;
61
62 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT ) RETURNS SETOF actor.org_unit AS $$
63     WITH RECURSIVE descendant_depth AS (
64         SELECT  ou.id,
65                 ou.parent_ou,
66                 out.depth
67           FROM  actor.org_unit ou
68                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
69           WHERE ou.id = $1
70             UNION ALL
71         SELECT  ou.id,
72                 ou.parent_ou,
73                 out.depth
74           FROM  actor.org_unit ou
75                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
76                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
77     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
78 $$ LANGUAGE SQL;
79
80 CREATE OR REPLACE FUNCTION actor.org_unit_ancestors( INT ) RETURNS SETOF actor.org_unit AS $$
81     WITH RECURSIVE anscestor_depth AS (
82         SELECT  ou.id,
83                 ou.parent_ou
84           FROM  actor.org_unit ou
85           WHERE ou.id = $1
86             UNION ALL
87         SELECT  ou.id,
88                 ou.parent_ou
89           FROM  actor.org_unit ou
90                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
91     ) SELECT ou.* FROM actor.org_unit ou JOIN anscestor_depth USING (id);
92 $$ LANGUAGE SQL;
93
94 -- Support merge template buckets
95 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('template_merge','Template Merge Container');
96
97 -- Recreate one of the constraints that we just dropped,
98 -- under a different name:
99
100 ALTER TABLE booking.resource_type
101         ADD CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record);
102
103 -- Now upgrade permission.perm_list.  This is fairly complicated.
104
105 -- Add ON UPDATE CASCADE to some foreign keys so that, when we renumber the
106 -- permissions, the dependents will follow and stay in sync:
107
108 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
109     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
110
111 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_perm_map_perm_fkey FOREIGN KEY (perm)
112     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
113
114 ALTER TABLE permission.usr_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
115     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
116
117 UPDATE permission.perm_list
118     SET code = 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow'
119     WHERE code = 'UPDATE_ORG_UNIT_SETTING.global.credit.allow';
120
121 -- The following UPDATES were originally in an individual upgrade script, but should
122 -- no longer be necessary now that the foreign key has an ON UPDATE CASCADE clause.
123 -- We retain the UPDATES here, commented out, as historical relics.
124
125 -- UPDATE permission.grp_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
126 -- UPDATE permission.usr_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
127
128 -- Spelling correction
129 UPDATE permission.perm_list SET code = 'ADMIN_RECURRING_FINE_RULE' WHERE code = 'ADMIN_RECURING_FINE_RULE';
130
131 -- Now we engage in a Great Renumbering of the permissions in permission.perm_list,
132 -- in order to clean up accumulated cruft.
133
134 -- The first step is to establish some triggers so that, when we change the id of a permission,
135 -- the associated translations are updated accordingly.
136
137 CREATE OR REPLACE FUNCTION oils_i18n_update_apply(old_ident TEXT, new_ident TEXT, hint TEXT) RETURNS VOID AS $_$
138 BEGIN
139
140     EXECUTE $$
141         UPDATE  config.i18n_core
142           SET   identity_value = $$ || quote_literal( new_ident ) || $$ 
143           WHERE fq_field LIKE '$$ || hint || $$.%' 
144                 AND identity_value = $$ || quote_literal( old_ident ) || $$;$$;
145
146     RETURN;
147
148 END;
149 $_$ LANGUAGE PLPGSQL;
150
151 CREATE OR REPLACE FUNCTION oils_i18n_id_tracking(/* hint */) RETURNS TRIGGER AS $_$
152 BEGIN
153     PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
154     RETURN NEW;
155 END;
156 $_$ LANGUAGE PLPGSQL;
157
158 CREATE OR REPLACE FUNCTION oils_i18n_code_tracking(/* hint */) RETURNS TRIGGER AS $_$
159 BEGIN
160     PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
161     RETURN NEW;
162 END;
163 $_$ LANGUAGE PLPGSQL;
164
165
166 CREATE TRIGGER maintain_perm_i18n_tgr
167     AFTER UPDATE ON permission.perm_list
168     FOR EACH ROW EXECUTE PROCEDURE oils_i18n_id_tracking('ppl');
169
170 -- Next, create a new table as a convenience for sloshing data back and forth,
171 -- and for recording which permission went where.  It looks just like
172 -- permission.perm_list, but with two extra columns: one for the old id, and one to
173 -- distinguish between predefined permissions and non-predefined permissions.
174
175 -- This table is, in effect, a temporary table, because we can drop it once the
176 -- upgrade is complete.  It is not technically temporary as far as PostgreSQL is
177 -- concerned, because we don't want it to disappear at the end of the session.
178 -- We keep it around so that we have a map showing the old id and the new id for
179 -- each permission.  However there is no IDL entry for it, nor is it defined
180 -- in the base sql files.
181
182 CREATE TABLE permission.temp_perm (
183         id          INT        PRIMARY KEY,
184         code        TEXT       UNIQUE,
185         description TEXT,
186         old_id      INT,
187         predefined  BOOL       NOT NULL DEFAULT TRUE
188 );
189
190 -- Populate the temp table with a definitive set of predefined permissions,
191 -- hard-coding the ids.
192
193 -- The first set of permissions is derived from the database, as loaded in a
194 -- loaded 1.6.1 database, plus a few changes previously applied in this upgrade
195 -- script.  The second set is derived from the IDL -- permissions that are referenced
196 -- in <permacrud> elements but not defined in the database.
197
198 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( -1, 'EVERYTHING',
199      '' );
200 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 1, 'OPAC_LOGIN',
201      'Allow a user to log in to the OPAC' );
202 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 2, 'STAFF_LOGIN',
203      'Allow a user to log in to the staff client' );
204 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 3, 'MR_HOLDS',
205      'Allow a user to create a metarecord holds' );
206 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 4, 'TITLE_HOLDS',
207      'Allow a user to place a hold at the title level' );
208 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 5, 'VOLUME_HOLDS',
209      'Allow a user to place a volume level hold' );
210 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 6, 'COPY_HOLDS',
211      'Allow a user to place a hold on a specific copy' );
212 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 7, 'REQUEST_HOLDS',
213      '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)' );
214 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 8, 'REQUEST_HOLDS_OVERRIDE',
215      '* no longer applicable' );
216 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 9, 'VIEW_HOLD',
217      'Allow a user to view another user''s holds' );
218 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 10, 'DELETE_HOLDS',
219      '* no longer applicable' );
220 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 11, 'UPDATE_HOLD',
221      'Allow a user to update another user''s hold' );
222 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 12, 'RENEW_CIRC',
223      'Allow a user to renew items' );
224 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 13, 'VIEW_USER_FINES_SUMMARY',
225      'Allow a user to view bill details' );
226 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 14, 'VIEW_USER_TRANSACTIONS',
227      'Allow a user to see another user''s grocery or circulation transactions in the Bills Interface; duplicate of VIEW_TRANSACTION' );
228 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 15, 'UPDATE_MARC',
229      'Allow a user to edit a MARC record' );
230 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 16, 'CREATE_MARC',
231      'Allow a user to create new MARC records' );
232 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 17, 'IMPORT_MARC',
233      'Allow a user to import a MARC record via the Z39.50 interface' );
234 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 18, 'CREATE_VOLUME',
235      'Allow a user to create a volume' );
236 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 19, 'UPDATE_VOLUME',
237      '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.' );
238 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 20, 'DELETE_VOLUME',
239      'Allow a user to delete a volume' );
240 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 21, 'CREATE_COPY',
241      'Allow a user to create a new copy object' );
242 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 22, 'UPDATE_COPY',
243      'Allow a user to edit a copy' );
244 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 23, 'DELETE_COPY',
245      'Allow a user to delete a copy' );
246 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 24, 'RENEW_HOLD_OVERRIDE',
247      'Allow a user to continue to renew an item even if it is required for a hold' );
248 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 25, 'CREATE_USER',
249      'Allow a user to create another user' );
250 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 26, 'UPDATE_USER',
251      'Allow a user to edit a user''s record' );
252 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 27, 'DELETE_USER',
253      'Allow a user to mark a user as deleted' );
254 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 28, 'VIEW_USER',
255      'Allow a user to view another user''s Patron Record' );
256 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 29, 'COPY_CHECKIN',
257      'Allow a user to check in a copy' );
258 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 30, 'CREATE_TRANSIT',
259      'Allow a user to place an item in transit' );
260 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 31, 'VIEW_PERMISSION',
261      'Allow a user to view user permissions within the user permissions editor' );
262 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 32, 'CHECKIN_BYPASS_HOLD_FULFILL',
263      '* no longer applicable' );
264 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 33, 'CREATE_PAYMENT',
265      'Allow a user to record payments in the Billing Interface' );
266 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 34, 'SET_CIRC_LOST',
267      'Allow a user to mark an item as ''lost''' );
268 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 35, 'SET_CIRC_MISSING',
269      'Allow a user to mark an item as ''missing''' );
270 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 36, 'SET_CIRC_CLAIMS_RETURNED',
271      'Allow a user to mark an item as ''claims returned''' );
272 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 37, 'CREATE_TRANSACTION',
273      'Allow a user to create a new billable transaction' );
274 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 38, 'VIEW_TRANSACTION',
275      'Allow a user may view another user''s transactions' );
276 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 39, 'CREATE_BILL',
277      'Allow a user to create a new bill on a transaction' );
278 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 40, 'VIEW_CONTAINER',
279      'Allow a user to view another user''s containers (buckets)' );
280 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 41, 'CREATE_CONTAINER',
281      'Allow a user to create a new container for another user' );
282 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 42, 'UPDATE_ORG_UNIT',
283      'Allow a user to change the settings for an organization unit' );
284 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 43, 'VIEW_CIRCULATIONS',
285      'Allow a user to see what another user has checked out' );
286 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 44, 'DELETE_CONTAINER',
287      'Allow a user to delete another user''s container' );
288 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 45, 'CREATE_CONTAINER_ITEM',
289      'Allow a user to create a container item for another user' );
290 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 46, 'CREATE_USER_GROUP_LINK',
291      'Allow a user to add other users to permission groups' );
292 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 47, 'REMOVE_USER_GROUP_LINK',
293      'Allow a user to remove other users from permission groups' );
294 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 48, 'VIEW_PERM_GROUPS',
295      'Allow a user to view other users'' permission groups' );
296 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 49, 'VIEW_PERMIT_CHECKOUT',
297      'Allow a user to determine whether another user can check out an item' );
298 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 50, 'UPDATE_BATCH_COPY',
299      'Allow a user to edit copies in batch' );
300 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 51, 'CREATE_PATRON_STAT_CAT',
301      'User may create a new patron statistical category' );
302 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 52, 'CREATE_COPY_STAT_CAT',
303      'User may create a copy statistical category' );
304 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 53, 'CREATE_PATRON_STAT_CAT_ENTRY',
305      'User may create an entry in a patron statistical category' );
306 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 54, 'CREATE_COPY_STAT_CAT_ENTRY',
307      'User may create an entry in a copy statistical category' );
308 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 55, 'UPDATE_PATRON_STAT_CAT',
309      'User may update a patron statistical category' );
310 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 56, 'UPDATE_COPY_STAT_CAT',
311      'User may update a copy statistical category' );
312 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 57, 'UPDATE_PATRON_STAT_CAT_ENTRY',
313      'User may update an entry in a patron statistical category' );
314 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 58, 'UPDATE_COPY_STAT_CAT_ENTRY',
315      'User may update an entry in a copy statistical category' );
316 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 59, 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
317      'User may link another user to an entry in a statistical category' );
318 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 60, 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
319      'User may link a copy to an entry in a statistical category' );
320 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 61, 'DELETE_PATRON_STAT_CAT',
321      'User may delete a patron statistical category' );
322 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 62, 'DELETE_COPY_STAT_CAT',
323      'User may delete a copy statistical category' );
324 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 63, 'DELETE_PATRON_STAT_CAT_ENTRY',
325      'User may delete an entry from a patron statistical category' );
326 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 64, 'DELETE_COPY_STAT_CAT_ENTRY',
327      'User may delete an entry from a copy statistical category' );
328 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 65, 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
329      'User may delete a patron statistical category entry map' );
330 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 66, 'DELETE_COPY_STAT_CAT_ENTRY_MAP',
331      'User may delete a copy statistical category entry map' );
332 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 67, 'CREATE_NON_CAT_TYPE',
333      'Allow a user to create a new non-cataloged item type' );
334 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 68, 'UPDATE_NON_CAT_TYPE',
335      'Allow a user to update a non-cataloged item type' );
336 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 69, 'CREATE_IN_HOUSE_USE',
337      'Allow a user to create a new in-house-use ' );
338 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 70, 'COPY_CHECKOUT',
339      'Allow a user to check out a copy' );
340 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 71, 'CREATE_COPY_LOCATION',
341      'Allow a user to create a new copy location' );
342 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 72, 'UPDATE_COPY_LOCATION',
343      'Allow a user to update a copy location' );
344 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 73, 'DELETE_COPY_LOCATION',
345      'Allow a user to delete a copy location' );
346 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 74, 'CREATE_COPY_TRANSIT',
347      'Allow a user to create a transit_copy object for transiting a copy' );
348 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 75, 'COPY_TRANSIT_RECEIVE',
349      'Allow a user to close out a transit on a copy' );
350 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 76, 'VIEW_HOLD_PERMIT',
351      'Allow a user to see if another user has permission to place a hold on a given copy' );
352 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 77, 'VIEW_COPY_CHECKOUT_HISTORY',
353      'Allow a user to view which users have checked out a given copy' );
354 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 78, 'REMOTE_Z3950_QUERY',
355      'Allow a user to perform Z39.50 queries against remote servers' );
356 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 79, 'REGISTER_WORKSTATION',
357      'Allow a user to register a new workstation' );
358 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 80, 'VIEW_COPY_NOTES',
359      'Allow a user to view all notes attached to a copy' );
360 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 81, 'VIEW_VOLUME_NOTES',
361      'Allow a user to view all notes attached to a volume' );
362 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 82, 'VIEW_TITLE_NOTES',
363      'Allow a user to view all notes attached to a title' );
364 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 83, 'CREATE_COPY_NOTE',
365      'Allow a user to create a new copy note' );
366 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 84, 'CREATE_VOLUME_NOTE',
367      'Allow a user to create a new volume note' );
368 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 85, 'CREATE_TITLE_NOTE',
369      'Allow a user to create a new title note' );
370 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 86, 'DELETE_COPY_NOTE',
371      'Allow a user to delete another user''s copy notes' );
372 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 87, 'DELETE_VOLUME_NOTE',
373      'Allow a user to delete another user''s volume note' );
374 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 88, 'DELETE_TITLE_NOTE',
375      'Allow a user to delete another user''s title note' );
376 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 89, 'UPDATE_CONTAINER',
377      'Allow a user to update another user''s container' );
378 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 90, 'CREATE_MY_CONTAINER',
379      'Allow a user to create a container for themselves' );
380 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 91, 'VIEW_HOLD_NOTIFICATION',
381      'Allow a user to view notifications attached to a hold' );
382 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 92, 'CREATE_HOLD_NOTIFICATION',
383      'Allow a user to create new hold notifications' );
384 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 93, 'UPDATE_ORG_SETTING',
385      'Allow a user to update an organization unit setting' );
386 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 94, 'OFFLINE_UPLOAD',
387      'Allow a user to upload an offline script' );
388 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 95, 'OFFLINE_VIEW',
389      'Allow a user to view uploaded offline script information' );
390 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 96, 'OFFLINE_EXECUTE',
391      'Allow a user to execute an offline script batch' );
392 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 97, 'CIRC_OVERRIDE_DUE_DATE',
393      'Allow a user to change the due date on an item to any date' );
394 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 98, 'CIRC_PERMIT_OVERRIDE',
395      'Allow a user to bypass the circulation permit call for check out' );
396 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 99, 'COPY_IS_REFERENCE.override',
397      'Allow a user to override the copy_is_reference event' );
398 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 100, 'VOID_BILLING',
399      'Allow a user to void a bill' );
400 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 101, 'CIRC_CLAIMS_RETURNED.override',
401      'Allow a user to check in or check out an item that has a status of ''claims returned''' );
402 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 102, 'COPY_BAD_STATUS.override',
403      'Allow a user to check out an item in a non-circulatable status' );
404 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 103, 'COPY_ALERT_MESSAGE.override',
405      'Allow a user to check in/out an item that has an alert message' );
406 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 104, 'COPY_STATUS_LOST.override',
407      'Allow a user to remove the lost status from a copy' );
408 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 105, 'COPY_STATUS_MISSING.override',
409      'Allow a user to change the missing status on a copy' );
410 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 106, 'ABORT_TRANSIT',
411      'Allow a user to abort a copy transit if the user is at the transit destination or source' );
412 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 107, 'ABORT_REMOTE_TRANSIT',
413      'Allow a user to abort a copy transit if the user is not at the transit source or dest' );
414 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 108, 'VIEW_ZIP_DATA',
415      'Allow a user to query the ZIP code data method' );
416 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 109, 'CANCEL_HOLDS',
417      'Allow a user to cancel holds' );
418 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 110, 'CREATE_DUPLICATE_HOLDS',
419      'Allow a user to create duplicate holds (two or more holds on the same title)' );
420 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 111, 'actor.org_unit.closed_date.delete',
421      'Allow a user to remove a closed date interval for a given location' );
422 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 112, 'actor.org_unit.closed_date.update',
423      'Allow a user to update a closed date interval for a given location' );
424 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 113, 'actor.org_unit.closed_date.create',
425      'Allow a user to create a new closed date for a location' );
426 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 114, 'DELETE_NON_CAT_TYPE',
427      'Allow a user to delete a non cataloged type' );
428 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 115, 'money.collections_tracker.create',
429      'Allow a user to put someone into collections' );
430 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 116, 'money.collections_tracker.delete',
431      'Allow a user to remove someone from collections' );
432 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 117, 'BAR_PATRON',
433      'Allow a user to bar a patron' );
434 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 118, 'UNBAR_PATRON',
435      'Allow a user to un-bar a patron' );
436 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 119, 'DELETE_WORKSTATION',
437      'Allow a user to remove an existing workstation so a new one can replace it' );
438 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 120, 'group_application.user',
439      'Allow a user to add/remove users to/from the "User" group' );
440 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 121, 'group_application.user.patron',
441      'Allow a user to add/remove users to/from the "Patron" group' );
442 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 122, 'group_application.user.staff',
443      'Allow a user to add/remove users to/from the "Staff" group' );
444 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 123, 'group_application.user.staff.circ',
445      'Allow a user to add/remove users to/from the "Circulator" group' );
446 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 124, 'group_application.user.staff.cat',
447      'Allow a user to add/remove users to/from the "Cataloger" group' );
448 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 125, 'group_application.user.staff.admin.global_admin',
449      'Allow a user to add/remove users to/from the "GlobalAdmin" group' );
450 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 126, 'group_application.user.staff.admin.local_admin',
451      'Allow a user to add/remove users to/from the "LocalAdmin" group' );
452 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 127, 'group_application.user.staff.admin.lib_manager',
453      'Allow a user to add/remove users to/from the "LibraryManager" group' );
454 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 128, 'group_application.user.staff.cat.cat1',
455      'Allow a user to add/remove users to/from the "Cat1" group' );
456 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 129, 'group_application.user.staff.supercat',
457      'Allow a user to add/remove users to/from the "Supercat" group' );
458 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 130, 'group_application.user.sip_client',
459      'Allow a user to add/remove users to/from the "SIP-Client" group' );
460 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 131, 'group_application.user.vendor',
461      'Allow a user to add/remove users to/from the "Vendor" group' );
462 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 132, 'ITEM_AGE_PROTECTED.override',
463      'Allow a user to place a hold on an age-protected item' );
464 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 133, 'MAX_RENEWALS_REACHED.override',
465      'Allow a user to renew an item past the maximum renewal count' );
466 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 134, 'PATRON_EXCEEDS_CHECKOUT_COUNT.override',
467      'Allow staff to override checkout count failure' );
468 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 135, 'PATRON_EXCEEDS_OVERDUE_COUNT.override',
469      'Allow staff to override overdue count failure' );
470 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 136, 'PATRON_EXCEEDS_FINES.override',
471      'Allow staff to override fine amount checkout failure' );
472 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 137, 'CIRC_EXCEEDS_COPY_RANGE.override',
473      'Allow staff to override circulation copy range failure' );
474 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 138, 'ITEM_ON_HOLDS_SHELF.override',
475      'Allow staff to override item on holds shelf failure' );
476 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 139, 'COPY_NOT_AVAILABLE.override',
477      'Allow staff to force checkout of Missing/Lost type items' );
478 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 140, 'HOLD_EXISTS.override',
479      'Allow a user to place multiple holds on a single title' );
480 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 141, 'RUN_REPORTS',
481      'Allow a user to run reports' );
482 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 142, 'SHARE_REPORT_FOLDER',
483      'Allow a user to share report his own folders' );
484 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 143, 'VIEW_REPORT_OUTPUT',
485      'Allow a user to view report output' );
486 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 144, 'COPY_CIRC_NOT_ALLOWED.override',
487      'Allow a user to checkout an item that is marked as non-circ' );
488 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 145, 'DELETE_CONTAINER_ITEM',
489      'Allow a user to delete an item out of another user''s container' );
490 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 146, 'ASSIGN_WORK_ORG_UNIT',
491      'Allow a staff member to define where another staff member has their permissions' );
492 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 147, 'CREATE_FUNDING_SOURCE',
493      'Allow a user to create a new funding source' );
494 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 148, 'DELETE_FUNDING_SOURCE',
495      'Allow a user to delete a funding source' );
496 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 149, 'VIEW_FUNDING_SOURCE',
497      'Allow a user to view a funding source' );
498 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 150, 'UPDATE_FUNDING_SOURCE',
499      'Allow a user to update a funding source' );
500 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 151, 'CREATE_FUND',
501      'Allow a user to create a new fund' );
502 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 152, 'DELETE_FUND',
503      'Allow a user to delete a fund' );
504 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 153, 'VIEW_FUND',
505      'Allow a user to view a fund' );
506 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 154, 'UPDATE_FUND',
507      'Allow a user to update a fund' );
508 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 155, 'CREATE_FUND_ALLOCATION',
509      'Allow a user to create a new fund allocation' );
510 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 156, 'DELETE_FUND_ALLOCATION',
511      'Allow a user to delete a fund allocation' );
512 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 157, 'VIEW_FUND_ALLOCATION',
513      'Allow a user to view a fund allocation' );
514 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 158, 'UPDATE_FUND_ALLOCATION',
515      'Allow a user to update a fund allocation' );
516 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 159, 'GENERAL_ACQ',
517      'Lowest level permission required to access the ACQ interface' );
518 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 160, 'CREATE_PROVIDER',
519      'Allow a user to create a new provider' );
520 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 161, 'DELETE_PROVIDER',
521      'Allow a user to delate a provider' );
522 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 162, 'VIEW_PROVIDER',
523      'Allow a user to view a provider' );
524 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 163, 'UPDATE_PROVIDER',
525      'Allow a user to update a provider' );
526 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 164, 'ADMIN_FUNDING_SOURCE',
527      'Allow a user to create/view/update/delete a funding source' );
528 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 165, 'ADMIN_FUND',
529      '(Deprecated) Allow a user to create/view/update/delete a fund' );
530 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 166, 'MANAGE_FUNDING_SOURCE',
531      'Allow a user to view/credit/debit a funding source' );
532 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 167, 'MANAGE_FUND',
533      'Allow a user to view/credit/debit a fund' );
534 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 168, 'CREATE_PICKLIST',
535      'Allows a user to create a picklist' );
536 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 169, 'ADMIN_PROVIDER',
537      'Allow a user to create/view/update/delete a provider' );
538 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 170, 'MANAGE_PROVIDER',
539      'Allow a user to view and purchase from a provider' );
540 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 171, 'VIEW_PICKLIST',
541      'Allow a user to view another users picklist' );
542 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 172, 'DELETE_RECORD',
543      'Allow a staff member to directly remove a bibliographic record' );
544 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 173, 'ADMIN_CURRENCY_TYPE',
545      'Allow a user to create/view/update/delete a currency_type' );
546 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 174, 'MARK_BAD_DEBT',
547      'Allow a user to mark a transaction as bad (unrecoverable) debt' );
548 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 175, 'VIEW_BILLING_TYPE',
549      'Allow a user to view billing types' );
550 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 176, 'MARK_ITEM_AVAILABLE',
551      'Allow a user to mark an item status as ''available''' );
552 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 177, 'MARK_ITEM_CHECKED_OUT',
553      'Allow a user to mark an item status as ''checked out''' );
554 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 178, 'MARK_ITEM_BINDERY',
555      'Allow a user to mark an item status as ''bindery''' );
556 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 179, 'MARK_ITEM_LOST',
557      'Allow a user to mark an item status as ''lost''' );
558 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 180, 'MARK_ITEM_MISSING',
559      'Allow a user to mark an item status as ''missing''' );
560 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 181, 'MARK_ITEM_IN_PROCESS',
561      'Allow a user to mark an item status as ''in process''' );
562 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 182, 'MARK_ITEM_IN_TRANSIT',
563      'Allow a user to mark an item status as ''in transit''' );
564 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 183, 'MARK_ITEM_RESHELVING',
565      'Allow a user to mark an item status as ''reshelving''' );
566 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 184, 'MARK_ITEM_ON_HOLDS_SHELF',
567      'Allow a user to mark an item status as ''on holds shelf''' );
568 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 185, 'MARK_ITEM_ON_ORDER',
569      'Allow a user to mark an item status as ''on order''' );
570 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 186, 'MARK_ITEM_ILL',
571      'Allow a user to mark an item status as ''inter-library loan''' );
572 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 187, 'group_application.user.staff.acq',
573      'Allows a user to add/remove/edit users in the "ACQ" group' );
574 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 188, 'CREATE_PURCHASE_ORDER',
575      'Allows a user to create a purchase order' );
576 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 189, 'VIEW_PURCHASE_ORDER',
577      'Allows a user to view a purchase order' );
578 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 190, 'IMPORT_ACQ_LINEITEM_BIB_RECORD',
579      'Allows a user to import a bib record from the acq staging area (on-order record) into the ILS bib data set' );
580 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 191, 'RECEIVE_PURCHASE_ORDER',
581      'Allows a user to mark a purchase order, lineitem, or individual copy as received' );
582 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 192, 'VIEW_ORG_SETTINGS',
583      'Allows a user to view all org settings at the specified level' );
584 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 193, 'CREATE_MFHD_RECORD',
585      'Allows a user to create a new MFHD record' );
586 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 194, 'UPDATE_MFHD_RECORD',
587      'Allows a user to update an MFHD record' );
588 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 195, 'DELETE_MFHD_RECORD',
589      'Allows a user to delete an MFHD record' );
590 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 196, 'ADMIN_ACQ_FUND',
591      'Allow a user to create/view/update/delete a fund' );
592 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 197, 'group_application.user.staff.acq_admin',
593      'Allows a user to add/remove/edit users in the "Acquisitions Administrators" group' );
594 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 198, 'SET_CIRC_CLAIMS_RETURNED.override',
595      'Allows staff to override the max claims returned value for a patron' );
596 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 199, 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
597      'Allows staff to manually change a patron''s claims returned count' );
598 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 200, 'UPDATE_BILL_NOTE',
599      'Allows staff to edit the note for a bill on a transaction' );
600 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 201, 'UPDATE_PAYMENT_NOTE',
601      'Allows staff to edit the note for a payment on a transaction' );
602 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 202, 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
603      'Allows staff to manually change a patron''s claims never checkout out count' );
604 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 203, 'ADMIN_COPY_LOCATION_ORDER',
605      'Allow a user to create/view/update/delete a copy location order' );
606 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 204, 'ASSIGN_GROUP_PERM',
607      '' );
608 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 205, 'CREATE_AUDIENCE',
609      '' );
610 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 206, 'CREATE_BIB_LEVEL',
611      '' );
612 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 207, 'CREATE_CIRC_DURATION',
613      '' );
614 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 208, 'CREATE_CIRC_MOD',
615      '' );
616 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 209, 'CREATE_COPY_STATUS',
617      '' );
618 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 210, 'CREATE_HOURS_OF_OPERATION',
619      '' );
620 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 211, 'CREATE_ITEM_FORM',
621      '' );
622 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 212, 'CREATE_ITEM_TYPE',
623      '' );
624 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 213, 'CREATE_LANGUAGE',
625      '' );
626 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 214, 'CREATE_LASSO',
627      '' );
628 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 215, 'CREATE_LASSO_MAP',
629      '' );
630 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 216, 'CREATE_LIT_FORM',
631      '' );
632 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 217, 'CREATE_METABIB_FIELD',
633      '' );
634 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 218, 'CREATE_NET_ACCESS_LEVEL',
635      '' );
636 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 219, 'CREATE_ORG_ADDRESS',
637      '' );
638 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 220, 'CREATE_ORG_TYPE',
639      '' );
640 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 221, 'CREATE_ORG_UNIT',
641      '' );
642 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 222, 'CREATE_ORG_UNIT_CLOSING',
643      '' );
644 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 223, 'CREATE_PERM',
645      '' );
646 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 224, 'CREATE_RELEVANCE_ADJUSTMENT',
647      '' );
648 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 225, 'CREATE_SURVEY',
649      '' );
650 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 226, 'CREATE_VR_FORMAT',
651      '' );
652 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 227, 'CREATE_XML_TRANSFORM',
653      '' );
654 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 228, 'DELETE_AUDIENCE',
655      '' );
656 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 229, 'DELETE_BIB_LEVEL',
657      '' );
658 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 230, 'DELETE_CIRC_DURATION',
659      '' );
660 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 231, 'DELETE_CIRC_MOD',
661      '' );
662 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 232, 'DELETE_COPY_STATUS',
663      '' );
664 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 233, 'DELETE_HOURS_OF_OPERATION',
665      '' );
666 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 234, 'DELETE_ITEM_FORM',
667      '' );
668 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 235, 'DELETE_ITEM_TYPE',
669      '' );
670 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 236, 'DELETE_LANGUAGE',
671      '' );
672 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 237, 'DELETE_LASSO',
673      '' );
674 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 238, 'DELETE_LASSO_MAP',
675      '' );
676 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 239, 'DELETE_LIT_FORM',
677      '' );
678 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 240, 'DELETE_METABIB_FIELD',
679      '' );
680 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 241, 'DELETE_NET_ACCESS_LEVEL',
681      '' );
682 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 242, 'DELETE_ORG_ADDRESS',
683      '' );
684 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 243, 'DELETE_ORG_TYPE',
685      '' );
686 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 244, 'DELETE_ORG_UNIT',
687      '' );
688 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 245, 'DELETE_ORG_UNIT_CLOSING',
689      '' );
690 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 246, 'DELETE_PERM',
691      '' );
692 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 247, 'DELETE_RELEVANCE_ADJUSTMENT',
693      '' );
694 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 248, 'DELETE_SURVEY',
695      '' );
696 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 249, 'DELETE_TRANSIT',
697      '' );
698 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 250, 'DELETE_VR_FORMAT',
699      '' );
700 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 251, 'DELETE_XML_TRANSFORM',
701      '' );
702 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 252, 'REMOVE_GROUP_PERM',
703      '' );
704 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 253, 'TRANSIT_COPY',
705      '' );
706 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 254, 'UPDATE_AUDIENCE',
707      '' );
708 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 255, 'UPDATE_BIB_LEVEL',
709      '' );
710 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 256, 'UPDATE_CIRC_DURATION',
711      '' );
712 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 257, 'UPDATE_CIRC_MOD',
713      '' );
714 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 258, 'UPDATE_COPY_NOTE',
715      '' );
716 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 259, 'UPDATE_COPY_STATUS',
717      '' );
718 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 260, 'UPDATE_GROUP_PERM',
719      '' );
720 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 261, 'UPDATE_HOURS_OF_OPERATION',
721      '' );
722 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 262, 'UPDATE_ITEM_FORM',
723      '' );
724 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 263, 'UPDATE_ITEM_TYPE',
725      '' );
726 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 264, 'UPDATE_LANGUAGE',
727      '' );
728 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 265, 'UPDATE_LASSO',
729      '' );
730 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 266, 'UPDATE_LASSO_MAP',
731      '' );
732 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 267, 'UPDATE_LIT_FORM',
733      '' );
734 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 268, 'UPDATE_METABIB_FIELD',
735      '' );
736 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 269, 'UPDATE_NET_ACCESS_LEVEL',
737      '' );
738 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 270, 'UPDATE_ORG_ADDRESS',
739      '' );
740 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 271, 'UPDATE_ORG_TYPE',
741      '' );
742 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 272, 'UPDATE_ORG_UNIT_CLOSING',
743      '' );
744 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 273, 'UPDATE_PERM',
745      '' );
746 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 274, 'UPDATE_RELEVANCE_ADJUSTMENT',
747      '' );
748 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 275, 'UPDATE_SURVEY',
749      '' );
750 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 276, 'UPDATE_TRANSIT',
751      '' );
752 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 277, 'UPDATE_VOLUME_NOTE',
753      '' );
754 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 278, 'UPDATE_VR_FORMAT',
755      '' );
756 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 279, 'UPDATE_XML_TRANSFORM',
757      '' );
758 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 280, 'MERGE_BIB_RECORDS',
759      '' );
760 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 281, 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
761      '' );
762 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 282, 'CREATE_ACQ_FUNDING_SOURCE',
763      '' );
764 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 283, 'CREATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
765      '' );
766 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 284, 'CREATE_AUTHORITY_IMPORT_QUEUE',
767      '' );
768 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 285, 'CREATE_AUTHORITY_RECORD_NOTE',
769      '' );
770 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 286, 'CREATE_BIB_IMPORT_FIELD_DEF',
771      '' );
772 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 287, 'CREATE_BIB_IMPORT_QUEUE',
773      '' );
774 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 288, 'CREATE_LOCALE',
775      '' );
776 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 289, 'CREATE_MARC_CODE',
777      '' );
778 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 290, 'CREATE_TRANSLATION',
779      '' );
780 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 291, 'DELETE_ACQ_FUNDING_SOURCE',
781      '' );
782 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 292, 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
783      '' );
784 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 293, 'DELETE_AUTHORITY_IMPORT_QUEUE',
785      '' );
786 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 294, 'DELETE_AUTHORITY_RECORD_NOTE',
787      '' );
788 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 295, 'DELETE_BIB_IMPORT_IMPORT_FIELD_DEF',
789      '' );
790 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 296, 'DELETE_BIB_IMPORT_QUEUE',
791      '' );
792 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 297, 'DELETE_LOCALE',
793      '' );
794 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 298, 'DELETE_MARC_CODE',
795      '' );
796 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 299, 'DELETE_TRANSLATION',
797      '' );
798 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 300, 'UPDATE_ACQ_FUNDING_SOURCE',
799      '' );
800 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 301, 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
801      '' );
802 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 302, 'UPDATE_AUTHORITY_IMPORT_QUEUE',
803      '' );
804 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 303, 'UPDATE_AUTHORITY_RECORD_NOTE',
805      '' );
806 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 304, 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
807      '' );
808 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 305, 'UPDATE_BIB_IMPORT_QUEUE',
809      '' );
810 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 306, 'UPDATE_LOCALE',
811      '' );
812 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 307, 'UPDATE_MARC_CODE',
813      '' );
814 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 308, 'UPDATE_TRANSLATION',
815      '' );
816 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 309, 'VIEW_ACQ_FUNDING_SOURCE',
817      '' );
818 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 310, 'VIEW_AUTHORITY_RECORD_NOTES',
819      '' );
820 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 311, 'CREATE_IMPORT_ITEM',
821      '' );
822 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 312, 'CREATE_IMPORT_ITEM_ATTR_DEF',
823      '' );
824 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 313, 'CREATE_IMPORT_TRASH_FIELD',
825      '' );
826 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 314, 'DELETE_IMPORT_ITEM',
827      '' );
828 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 315, 'DELETE_IMPORT_ITEM_ATTR_DEF',
829      '' );
830 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 316, 'DELETE_IMPORT_TRASH_FIELD',
831      '' );
832 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 317, 'UPDATE_IMPORT_ITEM',
833      '' );
834 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 318, 'UPDATE_IMPORT_ITEM_ATTR_DEF',
835      '' );
836 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 319, 'UPDATE_IMPORT_TRASH_FIELD',
837      '' );
838 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 320, 'UPDATE_ORG_UNIT_SETTING_ALL',
839      '' );
840 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 321, 'UPDATE_ORG_UNIT_SETTING.circ.lost_materials_processing_fee',
841      '' );
842 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 322, 'UPDATE_ORG_UNIT_SETTING.cat.default_item_price',
843      '' );
844 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 323, 'UPDATE_ORG_UNIT_SETTING.auth.opac_timeout',
845      '' );
846 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 324, 'UPDATE_ORG_UNIT_SETTING.auth.staff_timeout',
847      '' );
848 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 325, 'UPDATE_ORG_UNIT_SETTING.org.bounced_emails',
849      '' );
850 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 326, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_alert_interval',
851      '' );
852 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 327, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_interval',
853      '' );
854 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 328, 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow',
855      '' );
856 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 329, 'UPDATE_ORG_UNIT_SETTING.circ.void_overdue_on_lost',
857      '' );
858 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 330, 'UPDATE_ORG_UNIT_SETTING.circ.hold_stalling.soft',
859      '' );
860 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 331, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.hard',
861      '' );
862 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 332, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.soft',
863      '' );
864 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 333, 'UPDATE_ORG_UNIT_SETTING.opac.barcode_regex',
865      '' );
866 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 334, 'UPDATE_ORG_UNIT_SETTING.global.password_regex',
867      '' );
868 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 335, 'UPDATE_ORG_UNIT_SETTING.circ.item_checkout_history.max',
869      '' );
870 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 336, 'UPDATE_ORG_UNIT_SETTING.circ.reshelving_complete.interval',
871      '' );
872 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 337, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.patron_login_timeout',
873      '' );
874 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 338, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.alert_on_checkout_event',
875      '' );
876 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 339, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.require_patron_password',
877      '' );
878 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 340, 'UPDATE_ORG_UNIT_SETTING.global.juvenile_age_threshold',
879      '' );
880 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 341, 'UPDATE_ORG_UNIT_SETTING.cat.bib.keep_on_empty',
881      '' );
882 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 342, 'UPDATE_ORG_UNIT_SETTING.cat.bib.alert_on_empty',
883      '' );
884 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 343, 'UPDATE_ORG_UNIT_SETTING.patron.password.use_phone',
885      '' );
886 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 344, 'HOLD_ITEM_CHECKED_OUT.override',
887      'Allows a user to place a hold on an item that they already have checked out' );
888 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 345, 'ADMIN_ACQ_CANCEL_CAUSE',
889      'Allow a user to create/update/delete reasons for order cancellations' );
890 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 346, 'ACQ_XFER_MANUAL_DFUND_AMOUNT',
891      'Allow a user to transfer different amounts of money out of one fund and into another' );
892 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 347, 'OVERRIDE_HOLD_HAS_LOCAL_COPY',
893      'Allow a user to override the circ.holds.hold_has_copy_at.block setting' );
894 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 348, 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
895      'Allow a user to change the pickup and transit destination for a captured hold item already in transit' );
896 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 349, 'COPY_NEEDED_FOR_HOLD.override',
897      'Allow a user to force renewal of an item that could fulfill a hold request' );
898 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 350, 'MERGE_AUTH_RECORDS',
899      'Allow a user to merge authority records together' );
900 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 351, 'ALLOW_ALT_TCN',
901      'Allows staff to import a record using an alternate TCN to avoid conflicts' );
902 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 352, 'ADMIN_TRIGGER_EVENT_DEF',
903      'Allow a user to administer trigger event definitions' );
904 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 353, 'ADMIN_TRIGGER_CLEANUP',
905      'Allow a user to create, delete, and update trigger cleanup entries' );
906 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 354, 'CREATE_TRIGGER_CLEANUP',
907      'Allow a user to create trigger cleanup entries' );
908 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 355, 'DELETE_TRIGGER_CLEANUP',
909      'Allow a user to delete trigger cleanup entries' );
910 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 356, 'UPDATE_TRIGGER_CLEANUP',
911      'Allow a user to update trigger cleanup entries' );
912 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 357, 'CREATE_TRIGGER_EVENT_DEF',
913      'Allow a user to create trigger event definitions' );
914 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 358, 'DELETE_TRIGGER_EVENT_DEF',
915      'Allow a user to delete trigger event definitions' );
916 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 359, 'UPDATE_TRIGGER_EVENT_DEF',
917      'Allow a user to update trigger event definitions' );
918 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 360, 'VIEW_TRIGGER_EVENT_DEF',
919      'Allow a user to view trigger event definitions' );
920 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 361, 'ADMIN_TRIGGER_HOOK',
921      'Allow a user to create, update, and delete trigger hooks' );
922 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 362, 'CREATE_TRIGGER_HOOK',
923      'Allow a user to create trigger hooks' );
924 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 363, 'DELETE_TRIGGER_HOOK',
925      'Allow a user to delete trigger hooks' );
926 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 364, 'UPDATE_TRIGGER_HOOK',
927      'Allow a user to update trigger hooks' );
928 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 365, 'ADMIN_TRIGGER_REACTOR',
929      'Allow a user to create, update, and delete trigger reactors' );
930 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 366, 'CREATE_TRIGGER_REACTOR',
931      'Allow a user to create trigger reactors' );
932 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 367, 'DELETE_TRIGGER_REACTOR',
933      'Allow a user to delete trigger reactors' );
934 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 368, 'UPDATE_TRIGGER_REACTOR',
935      'Allow a user to update trigger reactors' );
936 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 369, 'ADMIN_TRIGGER_TEMPLATE_OUTPUT',
937      'Allow a user to delete trigger template output' );
938 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 370, 'DELETE_TRIGGER_TEMPLATE_OUTPUT',
939      'Allow a user to delete trigger template output' );
940 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 371, 'ADMIN_TRIGGER_VALIDATOR',
941      'Allow a user to create, update, and delete trigger validators' );
942 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 372, 'CREATE_TRIGGER_VALIDATOR',
943      'Allow a user to create trigger validators' );
944 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 373, 'DELETE_TRIGGER_VALIDATOR',
945      'Allow a user to delete trigger validators' );
946 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 374, 'UPDATE_TRIGGER_VALIDATOR',
947      'Allow a user to update trigger validators' );
948 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 376, 'ADMIN_BOOKING_RESOURCE',
949      'Enables the user to create/update/delete booking resources' );
950 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 377, 'ADMIN_BOOKING_RESOURCE_TYPE',
951      'Enables the user to create/update/delete booking resource types' );
952 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 378, 'ADMIN_BOOKING_RESOURCE_ATTR',
953      'Enables the user to create/update/delete booking resource attributes' );
954 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 379, 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
955      'Enables the user to create/update/delete booking resource attribute maps' );
956 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 380, 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
957      'Enables the user to create/update/delete booking resource attribute values' );
958 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 381, 'ADMIN_BOOKING_RESERVATION',
959      'Enables the user to create/update/delete booking reservations' );
960 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 382, 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
961      'Enables the user to create/update/delete booking reservation attribute value maps' );
962 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 383, 'RETRIEVE_RESERVATION_PULL_LIST',
963      'Allows a user to retrieve a booking reservation pull list' );
964 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 384, 'CAPTURE_RESERVATION',
965      'Allows a user to capture booking reservations' );
966 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 385, 'UPDATE_RECORD',
967      '' );
968 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 386, 'UPDATE_ORG_UNIT_SETTING.circ.block_renews_for_holds',
969      '' );
970 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 387, 'MERGE_USERS',
971      'Allows user records to be merged' );
972 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 388, 'ISSUANCE_HOLDS',
973      'Allow a user to place holds on serials issuances' );
974 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 389, 'VIEW_CREDIT_CARD_PROCESSING',
975      'View org unit settings related to credit card processing' );
976 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 390, 'ADMIN_CREDIT_CARD_PROCESSING',
977      'Update org unit settings related to credit card processing' );
978 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 391, 'ADMIN_SERIAL_CAPTION_PATTERN',
979         'Create/update/delete serial caption and pattern objects' );
980 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 392, 'ADMIN_SERIAL_SUBSCRIPTION',
981         'Create/update/delete serial subscription objects' );
982 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 393, 'ADMIN_SERIAL_DISTRIBUTION',
983         'Create/update/delete serial distribution objects' );
984 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 394, 'ADMIN_SERIAL_STREAM',
985         'Create/update/delete serial stream objects' );
986 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 395, 'RECEIVE_SERIAL',
987         'Receive serial items' );
988
989 -- Now for the permissions from the IDL.  We don't have descriptions for them.
990
991 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 396, 'ADMIN_ACQ_CLAIM' );
992 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 397, 'ADMIN_ACQ_CLAIM_EVENT_TYPE' );
993 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 398, 'ADMIN_ACQ_CLAIM_TYPE' );
994 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 399, 'ADMIN_ACQ_DISTRIB_FORMULA' );
995 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 400, 'ADMIN_ACQ_FISCAL_YEAR' );
996 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 401, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT' );
997 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 402, 'ADMIN_ACQ_FUND_TAG' );
998 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 403, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT' );
999 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 404, 'ADMIN_AGE_PROTECT_RULE' );
1000 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 405, 'ADMIN_ASSET_COPY_TEMPLATE' );
1001 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 406, 'ADMIN_BOOKING_RESERVATION_ATTR_MAP' );
1002 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 407, 'ADMIN_CIRC_MATRIX_MATCHPOINT' );
1003 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 408, 'ADMIN_CIRC_MOD' );
1004 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 409, 'ADMIN_CLAIM_POLICY' );
1005 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 410, 'ADMIN_CONFIG_REMOTE_ACCOUNT' );
1006 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 411, 'ADMIN_FIELD_DOC' );
1007 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 412, 'ADMIN_GLOBAL_FLAG' );
1008 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 413, 'ADMIN_GROUP_PENALTY_THRESHOLD' );
1009 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 414, 'ADMIN_HOLD_CANCEL_CAUSE' );
1010 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 415, 'ADMIN_HOLD_MATRIX_MATCHPOINT' );
1011 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 416, 'ADMIN_IDENT_TYPE' );
1012 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 417, 'ADMIN_IMPORT_ITEM_ATTR_DEF' );
1013 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 418, 'ADMIN_INDEX_NORMALIZER' );
1014 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 419, 'ADMIN_INVOICE' );
1015 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 420, 'ADMIN_INVOICE_METHOD' );
1016 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 421, 'ADMIN_INVOICE_PAYMENT_METHOD' );
1017 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 422, 'ADMIN_LINEITEM_MARC_ATTR_DEF' );
1018 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 423, 'ADMIN_MARC_CODE' );
1019 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 424, 'ADMIN_MAX_FINE_RULE' );
1020 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 425, 'ADMIN_MERGE_PROFILE' );
1021 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 426, 'ADMIN_ORG_UNIT_SETTING_TYPE' );
1022 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 427, 'ADMIN_RECURRING_FINE_RULE' );
1023 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 428, 'ADMIN_STANDING_PENALTY' );
1024 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 429, 'ADMIN_SURVEY' );
1025 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 430, 'ADMIN_USER_REQUEST_TYPE' );
1026 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 431, 'ADMIN_USER_SETTING_GROUP' );
1027 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 432, 'ADMIN_USER_SETTING_TYPE' );
1028 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 433, 'ADMIN_Z3950_SOURCE' );
1029 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 434, 'CREATE_BIB_BTYPE' );
1030 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 435, 'CREATE_BIBLIO_FINGERPRINT' );
1031 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 436, 'CREATE_BIB_SOURCE' );
1032 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 437, 'CREATE_BILLING_TYPE' );
1033 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 438, 'CREATE_CN_BTYPE' );
1034 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 439, 'CREATE_COPY_BTYPE' );
1035 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 440, 'CREATE_INVOICE' );
1036 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 441, 'CREATE_INVOICE_ITEM_TYPE' );
1037 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 442, 'CREATE_INVOICE_METHOD' );
1038 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 443, 'CREATE_MERGE_PROFILE' );
1039 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 444, 'CREATE_METABIB_CLASS' );
1040 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 445, 'CREATE_METABIB_SEARCH_ALIAS' );
1041 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 446, 'CREATE_USER_BTYPE' );
1042 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 447, 'DELETE_BIB_BTYPE' );
1043 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 448, 'DELETE_BIBLIO_FINGERPRINT' );
1044 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 449, 'DELETE_BIB_SOURCE' );
1045 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 450, 'DELETE_BILLING_TYPE' );
1046 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 451, 'DELETE_CN_BTYPE' );
1047 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 452, 'DELETE_COPY_BTYPE' );
1048 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 453, 'DELETE_INVOICE_ITEM_TYPE' );
1049 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 454, 'DELETE_INVOICE_METHOD' );
1050 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 455, 'DELETE_MERGE_PROFILE' );
1051 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 456, 'DELETE_METABIB_CLASS' );
1052 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 457, 'DELETE_METABIB_SEARCH_ALIAS' );
1053 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 458, 'DELETE_USER_BTYPE' );
1054 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 459, 'MANAGE_CLAIM' );
1055 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 460, 'UPDATE_BIB_BTYPE' );
1056 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 461, 'UPDATE_BIBLIO_FINGERPRINT' );
1057 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 462, 'UPDATE_BIB_SOURCE' );
1058 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 463, 'UPDATE_BILLING_TYPE' );
1059 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 464, 'UPDATE_CN_BTYPE' );
1060 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 465, 'UPDATE_COPY_BTYPE' );
1061 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 466, 'UPDATE_INVOICE_ITEM_TYPE' );
1062 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 467, 'UPDATE_INVOICE_METHOD' );
1063 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 468, 'UPDATE_MERGE_PROFILE' );
1064 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 469, 'UPDATE_METABIB_CLASS' );
1065 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 470, 'UPDATE_METABIB_SEARCH_ALIAS' );
1066 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 471, 'UPDATE_USER_BTYPE' );
1067 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 472, 'user_request.create' );
1068 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 473, 'user_request.delete' );
1069 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 474, 'user_request.update' );
1070 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 475, 'user_request.view' );
1071 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 476, 'VIEW_ACQ_FUND_ALLOCATION_PERCENT' );
1072 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 477, 'VIEW_CIRC_MATRIX_MATCHPOINT' );
1073 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 478, 'VIEW_CLAIM' );
1074 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 479, 'VIEW_GROUP_PENALTY_THRESHOLD' );
1075 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 480, 'VIEW_HOLD_MATRIX_MATCHPOINT' );
1076 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 481, 'VIEW_INVOICE' );
1077 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 482, 'VIEW_MERGE_PROFILE' );
1078 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 483, 'VIEW_SERIAL_SUBSCRIPTION' );
1079 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 484, 'VIEW_STANDING_PENALTY' );
1080
1081 -- For every permission in the temp_perm table that has a matching
1082 -- permission in the real table: record the original id.
1083
1084 UPDATE permission.temp_perm AS tp
1085 SET old_id =
1086         (
1087                 SELECT id
1088                 FROM permission.perm_list AS ppl
1089                 WHERE ppl.code = tp.code
1090         )
1091 WHERE code IN ( SELECT code FROM permission.perm_list );
1092
1093 -- Start juggling ids.
1094
1095 -- If any permissions have negative ids (with the special exception of -1),
1096 -- we need to move them into the positive range in order to avoid duplicate
1097 -- key problems (since we are going to use the negative range as a temporary
1098 -- staging area).
1099
1100 -- First, move any predefined permissions that have negative ids (again with
1101 -- the special exception of -1).  Temporarily give them positive ids based on
1102 -- the sequence.
1103
1104 UPDATE permission.perm_list
1105 SET id = NEXTVAL('permission.perm_list_id_seq'::regclass)
1106 WHERE id < -1
1107   AND code IN (SELECT code FROM permission.temp_perm);
1108
1109 -- Identify any non-predefined permissions whose ids are either negative
1110 -- or within the range (0-1000) reserved for predefined permissions.
1111 -- Assign them ids above 1000, based on the sequence.  Record the new
1112 -- ids in the temp_perm table.
1113
1114 INSERT INTO permission.temp_perm ( id, code, description, old_id, predefined )
1115 (
1116         SELECT NEXTVAL('permission.perm_list_id_seq'::regclass),
1117                 code, description, id, false
1118         FROM permission.perm_list
1119         WHERE  ( id < -1 OR id BETWEEN 0 AND 1000 )
1120         AND code NOT IN (SELECT code FROM permission.temp_perm)
1121 );
1122
1123 -- Now update the ids of those non-predefined permissions, using the
1124 -- values assigned in the previous step.
1125
1126 UPDATE permission.perm_list AS ppl
1127 SET id = (
1128                 SELECT id
1129                 FROM permission.temp_perm AS tp
1130                 WHERE tp.code = ppl.code
1131         )
1132 WHERE id IN ( SELECT old_id FROM permission.temp_perm WHERE NOT predefined );
1133
1134 -- Now the negative ids have been eliminated, except for -1.  Move all the
1135 -- predefined permissions temporarily into the negative range.
1136
1137 UPDATE permission.perm_list
1138 SET id = -1 - id
1139 WHERE id <> -1
1140 AND code IN ( SELECT code from permission.temp_perm WHERE predefined );
1141
1142 -- Apply the final ids to the existing predefined permissions.
1143
1144 UPDATE permission.perm_list AS ppl
1145 SET id =
1146         (
1147                 SELECT id
1148                 FROM permission.temp_perm AS tp
1149                 WHERE tp.code = ppl.code
1150         )
1151 WHERE
1152         id <> -1
1153         AND ppl.code IN
1154         (
1155                 SELECT code from permission.temp_perm
1156                 WHERE predefined
1157                 AND old_id IS NOT NULL
1158         );
1159
1160 -- If there are any predefined permissions that don't exist yet in
1161 -- permission.perm_list, insert them now.
1162
1163 INSERT INTO permission.perm_list ( id, code, description )
1164 (
1165         SELECT id, code, description
1166         FROM permission.temp_perm
1167         WHERE old_id IS NULL
1168 );
1169
1170 -- Reset the sequence to the lowest feasible value.  This may or may not
1171 -- accomplish anything, but it will do no harm.
1172
1173 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, GREATEST( 
1174         (SELECT MAX(id) FROM permission.perm_list), 1000 ));
1175
1176 -- If any permission lacks a description, use the code as a description.
1177 -- It's better than nothing.
1178
1179 UPDATE permission.perm_list
1180 SET description = code
1181 WHERE description IS NULL
1182    OR description = '';
1183
1184 -- Thus endeth the Great Renumbering.
1185
1186 -- Having massaged the permissions, massage the way they are assigned, by inserting
1187 -- rows into permission.grp_perm_map.  Some of these permissions may have already
1188 -- been assigned, so we insert the rows only if they aren't already there.
1189
1190 -- for backwards compat, give everyone the permission
1191 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1192     SELECT 1, id, 0, false FROM permission.perm_list AS perm
1193         WHERE code = 'HOLD_ITEM_CHECKED_OUT.override'
1194                 AND NOT EXISTS (
1195                         SELECT 1
1196                         FROM permission.grp_perm_map AS map
1197                         WHERE
1198                                 grp = 1
1199                                 AND map.perm = perm.id
1200                 );
1201
1202 -- Add trigger administration permissions to the Local System Administrator group.
1203 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1204     SELECT 10, id, 1, false FROM permission.perm_list AS perm
1205     WHERE (
1206                 perm.code LIKE 'ADMIN_TRIGGER%'
1207         OR perm.code LIKE 'CREATE_TRIGGER%'
1208         OR perm.code LIKE 'DELETE_TRIGGER%'
1209         OR perm.code LIKE 'UPDATE_TRIGGER%'
1210         ) AND NOT EXISTS (
1211                 SELECT 1
1212                 FROM permission.grp_perm_map AS map
1213                 WHERE
1214                         grp = 10
1215                         AND map.perm = perm.id
1216         );
1217
1218 -- View trigger permissions are required at a consortial level for initial setup
1219 -- (as before, only if the row doesn't already exist)
1220 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1221     SELECT 10, id, 0, false FROM permission.perm_list AS perm
1222         WHERE code LIKE 'VIEW_TRIGGER%'
1223                 AND NOT EXISTS (
1224                         SELECT 1
1225                         FROM permission.grp_perm_map AS map
1226                         WHERE
1227                                 grp = 10
1228                                 AND map.perm = perm.id
1229                 );
1230
1231 -- Permission for merging auth records may already be defined,
1232 -- so add it only if it isn't there.
1233 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1234     SELECT 4, id, 1, false FROM permission.perm_list AS perm
1235         WHERE code = 'MERGE_AUTH_RECORDS'
1236                 AND NOT EXISTS (
1237                         SELECT 1
1238                         FROM permission.grp_perm_map AS map
1239                         WHERE
1240                                 grp = 4
1241                                 AND map.perm = perm.id
1242                 );
1243
1244 -- Create a reference table as parent to both
1245 -- config.org_unit_setting_type and config_usr_setting_type
1246
1247 CREATE TABLE config.settings_group (
1248     name    TEXT PRIMARY KEY,
1249     label   TEXT UNIQUE NOT NULL -- I18N
1250 );
1251
1252 -- org_unit setting types
1253 CREATE TABLE config.org_unit_setting_type (
1254     name            TEXT    PRIMARY KEY,
1255     label           TEXT    UNIQUE NOT NULL,
1256     grp             TEXT    REFERENCES config.settings_group (name),
1257     description     TEXT,
1258     datatype        TEXT    NOT NULL DEFAULT 'string',
1259     fm_class        TEXT,
1260     view_perm       INT,
1261     update_perm     INT,
1262     --
1263     -- define valid datatypes
1264     --
1265     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1266     ( 'bool', 'integer', 'float', 'currency', 'interval',
1267       'date', 'string', 'object', 'array', 'link' ) ),
1268     --
1269     -- fm_class is meaningful only for 'link' datatype
1270     --
1271     CONSTRAINT coust_no_empty_link CHECK
1272     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
1273       ( datatype <> 'link' AND fm_class IS NULL ) ),
1274         CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id)
1275                 ON UPDATE CASCADE
1276                 ON DELETE RESTRICT
1277                 DEFERRABLE INITIALLY DEFERRED,
1278         CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id)
1279                 ON UPDATE CASCADE
1280                 DEFERRABLE INITIALLY DEFERRED
1281 );
1282
1283 CREATE TABLE config.usr_setting_type (
1284
1285     name TEXT PRIMARY KEY,
1286     opac_visible BOOL NOT NULL DEFAULT FALSE,
1287     label TEXT UNIQUE NOT NULL,
1288     description TEXT,
1289     grp             TEXT    REFERENCES config.settings_group (name),
1290     datatype TEXT NOT NULL DEFAULT 'string',
1291     fm_class TEXT,
1292
1293     --
1294     -- define valid datatypes
1295     --
1296     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1297     ( 'bool', 'integer', 'float', 'currency', 'interval',
1298         'date', 'string', 'object', 'array', 'link' ) ),
1299
1300     --
1301     -- fm_class is meaningful only for 'link' datatype
1302     --
1303     CONSTRAINT coust_no_empty_link CHECK
1304     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
1305         ( datatype <> 'link' AND fm_class IS NULL ) )
1306
1307 );
1308
1309 --------------------------------------
1310 -- Seed data for org_unit_setting_type
1311 --------------------------------------
1312
1313 INSERT into config.org_unit_setting_type
1314 ( name, label, description, datatype ) VALUES
1315
1316 ( 'auth.opac_timeout',
1317   'OPAC Inactivity Timeout (in seconds)',
1318   null,
1319   'integer' ),
1320
1321 ( 'auth.staff_timeout',
1322   'Staff Login Inactivity Timeout (in seconds)',
1323   null,
1324   'integer' ),
1325
1326 ( 'circ.lost_materials_processing_fee',
1327   'Lost Materials Processing Fee',
1328   null,
1329   'currency' ),
1330
1331 ( 'cat.default_item_price',
1332   'Default Item Price',
1333   null,
1334   'currency' ),
1335
1336 ( 'org.bounced_emails',
1337   'Sending email address for patron notices',
1338   null,
1339   'string' ),
1340
1341 ( 'circ.hold_expire_alert_interval',
1342   'Holds: Expire Alert Interval',
1343   'Amount of time before a hold expires at which point the patron should be alerted',
1344   'interval' ),
1345
1346 ( 'circ.hold_expire_interval',
1347   'Holds: Expire Interval',
1348   'Amount of time after a hold is placed before the hold expires.  Example "100 days"',
1349   'interval' ),
1350
1351 ( 'credit.payments.allow',
1352   'Allow Credit Card Payments',
1353   'If enabled, patrons will be able to pay fines accrued at this location via credit card',
1354   'bool' ),
1355
1356 ( 'global.default_locale',
1357   'Global Default Locale',
1358   null,
1359   'string' ),
1360
1361 ( 'circ.void_overdue_on_lost',
1362   'Void overdue fines when items are marked lost',
1363   null,
1364   'bool' ),
1365
1366 ( 'circ.hold_stalling.soft',
1367   'Holds: Soft stalling interval',
1368   'How long to wait before allowing remote items to be opportunistically captured for a hold.  Example "5 days"',
1369   'interval' ),
1370
1371 ( 'circ.hold_stalling_hard',
1372   'Holds: Hard stalling interval',
1373   '',
1374   'interval' ),
1375
1376 ( 'circ.hold_boundary.hard',
1377   'Holds: Hard boundary',
1378   null,
1379   'integer' ),
1380
1381 ( 'circ.hold_boundary.soft',
1382   'Holds: Soft boundary',
1383   null,
1384   'integer' ),
1385
1386 ( 'opac.barcode_regex',
1387   'Patron barcode format',
1388   'Regular expression defining the patron barcode format',
1389   'string' ),
1390
1391 ( 'global.password_regex',
1392   'Password format',
1393   'Regular expression defining the password format',
1394   'string' ),
1395
1396 ( 'circ.item_checkout_history.max',
1397   'Maximum previous checkouts displayed',
1398   'This is the maximum number of previous circulations the staff client will display when investigating item details',
1399   'integer' ),
1400
1401 ( 'circ.reshelving_complete.interval',
1402   'Change reshelving status interval',
1403   'Amount of time to wait before changing an item from "reshelving" status to "available".  Examples: "1 day", "6 hours"',
1404   'interval' ),
1405
1406 ( 'circ.holds.default_estimated_wait_interval',
1407   'Holds: Default Estimated Wait',
1408   '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.',
1409   'interval' ),
1410
1411 ( 'circ.holds.min_estimated_wait_interval',
1412   'Holds: Minimum Estimated Wait',
1413   '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.',
1414   'interval' ),
1415
1416 ( 'circ.selfcheck.patron_login_timeout',
1417   'Selfcheck: Patron Login Timeout (in seconds)',
1418   'Number of seconds of inactivity before the patron is logged out of the selfcheck interface',
1419   'integer' ),
1420
1421 ( 'circ.selfcheck.alert.popup',
1422   'Selfcheck: Pop-up alert for errors',
1423   'If true, checkout/renewal errors will cause a pop-up window in addition to the on-screen message',
1424   'bool' ),
1425
1426 ( 'circ.selfcheck.require_patron_password',
1427   'Selfcheck: Require patron password',
1428   'If true, patrons will be required to enter their password in addition to their username/barcode to log into the selfcheck interface',
1429   'bool' ),
1430
1431 ( 'global.juvenile_age_threshold',
1432   'Juvenile Age Threshold',
1433   'The age at which a user is no long considered a juvenile.  For example, "18 years".',
1434   'interval' ),
1435
1436 ( 'cat.bib.keep_on_empty',
1437   'Retain empty bib records',
1438   'Retain a bib record even when all attached copies are deleted',
1439   'bool' ),
1440
1441 ( 'cat.bib.alert_on_empty',
1442   'Alert on empty bib records',
1443   'Alert staff when the last copy for a record is being deleted',
1444   'bool' ),
1445
1446 ( 'patron.password.use_phone',
1447   'Patron: password from phone #',
1448   'Use the last 4 digits of the patrons phone number as the default password when creating new users',
1449   'bool' ),
1450
1451 ( 'circ.charge_on_damaged',
1452   'Charge item price when marked damaged',
1453   'Charge item price when marked damaged',
1454   'bool' ),
1455
1456 ( 'circ.charge_lost_on_zero',
1457   'Charge lost on zero',
1458   '',
1459   'bool' ),
1460
1461 ( 'circ.damaged_item_processing_fee',
1462   'Charge processing fee for damaged items',
1463   'Charge processing fee for damaged items',
1464   'currency' ),
1465
1466 ( 'circ.void_lost_on_checkin',
1467   'Circ: Void lost item billing when returned',
1468   'Void lost item billing when returned',
1469   'bool' ),
1470
1471 ( 'circ.max_accept_return_of_lost',
1472   'Circ: Void lost max interval',
1473   'Items that have been lost this long will not result in voided billings when returned.  E.g. ''6 months''',
1474   'interval' ),
1475
1476 ( 'circ.void_lost_proc_fee_on_checkin',
1477   'Circ: Void processing fee on lost item return',
1478   'Void processing fee when lost item returned',
1479   'bool' ),
1480
1481 ( 'circ.restore_overdue_on_lost_return',
1482   'Circ: Restore overdues on lost item return',
1483   'Restore overdue fines on lost item return',
1484   'bool' ),
1485
1486 ( 'circ.lost_immediately_available',
1487   'Circ: Lost items usable on checkin',
1488   'Lost items are usable on checkin instead of going ''home'' first',
1489   'bool' ),
1490
1491 ( 'circ.holds_fifo',
1492   'Holds: FIFO',
1493   'Force holds to a more strict First-In, First-Out capture',
1494   'bool' ),
1495
1496 ( 'opac.allow_pending_address',
1497   'OPAC: Allow pending addresses',
1498   'If enabled, patrons can create and edit existing addresses.  Addresses are kept in a pending state until staff approves the changes',
1499   'bool' ),
1500
1501 ( 'ui.circ.show_billing_tab_on_bills',
1502   'Show billing tab first when bills are present',
1503   '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',
1504   'bool' ),
1505
1506 ( 'ui.general.idle_timeout',
1507     'GUI: Idle timeout',
1508     '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).',
1509     'integer' ),
1510
1511 ( 'ui.circ.in_house_use.entry_cap',
1512   'GUI: Record In-House Use: Maximum # of uses allowed per entry.',
1513   'The # of uses entry in the Record In-House Use interface may not exceed the value of this setting.',
1514   'integer' ),
1515
1516 ( 'ui.circ.in_house_use.entry_warn',
1517   'GUI: Record In-House Use: # of uses threshold for Are You Sure? dialog.',
1518   'In the Record In-House Use interface, a submission attempt will warn if the # of uses field exceeds the value of this setting.',
1519   'integer' ),
1520
1521 ( 'acq.default_circ_modifier',
1522   'Default circulation modifier',
1523   null,
1524   'string' ),
1525
1526 ( 'acq.tmp_barcode_prefix',
1527   'Temporary barcode prefix',
1528   null,
1529   'string' ),
1530
1531 ( 'acq.tmp_callnumber_prefix',
1532   'Temporary call number prefix',
1533   null,
1534   'string' ),
1535
1536 ( 'ui.circ.patron_summary.horizontal',
1537   'Patron circulation summary is horizontal',
1538   null,
1539   'bool' ),
1540
1541 ( 'ui.staff.require_initials',
1542   oils_i18n_gettext('ui.staff.require_initials', 'GUI: Require staff initials for entry/edit of item/patron/penalty notes/messages.', 'coust', 'label'),
1543   oils_i18n_gettext('ui.staff.require_initials', 'Appends staff initials and edit date into note content.', 'coust', 'description'),
1544   'bool' ),
1545
1546 ( 'ui.general.button_bar',
1547   'Button bar',
1548   null,
1549   'bool' ),
1550
1551 ( 'circ.hold_shelf_status_delay',
1552   'Hold Shelf Status Delay',
1553   '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.',
1554   'interval' ),
1555
1556 ( 'circ.patron_invalid_address_apply_penalty',
1557   'Invalid patron address penalty',
1558   'When set, if a patron address is set to invalid, a penalty is applied.',
1559   'bool' ),
1560
1561 ( 'circ.checkout_fills_related_hold',
1562   'Checkout Fills Related Hold',
1563   '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',
1564   'bool'),
1565
1566 ( 'circ.selfcheck.auto_override_checkout_events',
1567   'Selfcheck override events list',
1568   'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction',
1569   'array' ),
1570
1571 ( 'circ.staff_client.do_not_auto_attempt_print',
1572   'Disable Automatic Print Attempt Type List',
1573   '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).',
1574   'array' ),
1575
1576 ( 'ui.patron.default_inet_access_level',
1577   'Default level of patrons'' internet access',
1578   null,
1579   'integer' ),
1580
1581 ( 'circ.max_patron_claim_return_count',
1582     'Max Patron Claims Returned Count',
1583     'When this count is exceeded, a staff override is required to mark the item as claims returned',
1584     'integer' ),
1585
1586 ( 'circ.obscure_dob',
1587     'Obscure the Date of Birth field',
1588     '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.',
1589     'bool' ),
1590
1591 ( 'circ.auto_hide_patron_summary',
1592     'GUI: Toggle off the patron summary sidebar after first view.',
1593     'When true, the patron summary sidebar will collapse after a new patron sub-interface is selected.',
1594     'bool' ),
1595
1596 ( 'credit.processor.default',
1597     'Credit card processing: Name default credit processor',
1598     'This can be "AuthorizeNet", "PayPal" (for the Website Payment Pro API), or "PayflowPro".',
1599     'string' ),
1600
1601 ( 'credit.processor.authorizenet.enabled',
1602     'Credit card processing: AuthorizeNet enabled',
1603     '',
1604     'bool' ),
1605
1606 ( 'credit.processor.authorizenet.login',
1607     'Credit card processing: AuthorizeNet login',
1608     '',
1609     'string' ),
1610
1611 ( 'credit.processor.authorizenet.password',
1612     'Credit card processing: AuthorizeNet password',
1613     '',
1614     'string' ),
1615
1616 ( 'credit.processor.authorizenet.server',
1617     'Credit card processing: AuthorizeNet server',
1618     'Required if using a developer/test account with AuthorizeNet',
1619     'string' ),
1620
1621 ( 'credit.processor.authorizenet.testmode',
1622     'Credit card processing: AuthorizeNet test mode',
1623     '',
1624     'bool' ),
1625
1626 ( 'credit.processor.paypal.enabled',
1627     'Credit card processing: PayPal enabled',
1628     '',
1629     'bool' ),
1630 ( 'credit.processor.paypal.login',
1631     'Credit card processing: PayPal login',
1632     '',
1633     'string' ),
1634 ( 'credit.processor.paypal.password',
1635     'Credit card processing: PayPal password',
1636     '',
1637     'string' ),
1638 ( 'credit.processor.paypal.signature',
1639     'Credit card processing: PayPal signature',
1640     '',
1641     'string' ),
1642 ( 'credit.processor.paypal.testmode',
1643     'Credit card processing: PayPal test mode',
1644     '',
1645     'bool' ),
1646
1647 ( 'ui.admin.work_log.max_entries',
1648     oils_i18n_gettext('ui.admin.work_log.max_entries', 'GUI: Work Log: Maximum Actions Logged', 'coust', 'label'),
1649     oils_i18n_gettext('ui.admin.work_log.max_entries', 'Maximum entries for "Most Recent Staff Actions" section of the Work Log interface.', 'coust', 'description'),
1650   'interval' ),
1651
1652 ( 'ui.admin.patron_log.max_entries',
1653     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'GUI: Work Log: Maximum Patrons Logged', 'coust', 'label'),
1654     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'Maximum entries for "Most Recently Affected Patrons..." section of the Work Log interface.', 'coust', 'description'),
1655   'interval' ),
1656
1657 ( 'lib.courier_code',
1658     oils_i18n_gettext('lib.courier_code', 'Courier Code', 'coust', 'label'),
1659     oils_i18n_gettext('lib.courier_code', 'Courier Code for the library.  Available in transit slip templates as the %courier_code% macro.', 'coust', 'description'),
1660     'string'),
1661
1662 ( 'circ.block_renews_for_holds',
1663     oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
1664     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'),
1665     'bool' ),
1666
1667 ( 'circ.password_reset_request_per_user_limit',
1668     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
1669     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'),
1670     'string'),
1671
1672 ( 'circ.password_reset_request_time_to_live',
1673     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
1674     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'),
1675     'string'),
1676
1677 ( 'circ.password_reset_request_throttle',
1678     oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
1679     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'),
1680     'string')
1681 ;
1682
1683 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1684         'ui.circ.suppress_checkin_popups',
1685         oils_i18n_gettext(
1686             'ui.circ.suppress_checkin_popups', 
1687             'Circ: Suppress popup-dialogs during check-in.', 
1688             'coust', 
1689             'label'),
1690         oils_i18n_gettext(
1691             'ui.circ.suppress_checkin_popups', 
1692             'Circ: Suppress popup-dialogs during check-in.', 
1693             'coust', 
1694             'description'),
1695         'bool'
1696 );
1697
1698 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1699         'format.date',
1700         oils_i18n_gettext(
1701             'format.date',
1702             'GUI: Format Dates with this pattern.', 
1703             'coust', 
1704             'label'),
1705         oils_i18n_gettext(
1706             'format.date',
1707             'GUI: Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010")', 
1708             'coust', 
1709             'description'),
1710         'string'
1711 ), (
1712         'format.time',
1713         oils_i18n_gettext(
1714             'format.time',
1715             'GUI: Format Times with this pattern.', 
1716             'coust', 
1717             'label'),
1718         oils_i18n_gettext(
1719             'format.time',
1720             '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")', 
1721             'coust', 
1722             'description'),
1723         'string'
1724 );
1725
1726 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1727         'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1728         oils_i18n_gettext(
1729             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1730             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1731             'coust', 
1732             'label'),
1733         oils_i18n_gettext(
1734             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1735             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1736             'coust', 
1737             'description'),
1738         'bool'
1739 );
1740
1741 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1742         'url.remote_column_settings',
1743         oils_i18n_gettext(
1744             'url.remote_column_settings',
1745             'GUI: URL for remote directory containing list column settings.', 
1746             'coust', 
1747             'label'),
1748         oils_i18n_gettext(
1749             'url.remote_column_settings',
1750             '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.', 
1751             'coust', 
1752             'description'),
1753         'string'
1754 );
1755
1756 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1757         'gui.disable_local_save_columns',
1758         oils_i18n_gettext(
1759             'gui.disable_local_save_columns',
1760             'GUI: Disable the ability to save list column configurations locally.', 
1761             'coust', 
1762             'label'),
1763         oils_i18n_gettext(
1764             'gui.disable_local_save_columns',
1765             '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.', 
1766             'coust', 
1767             'description'),
1768         'bool'
1769 );
1770
1771 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1772         'circ.password_reset_request_requires_matching_email',
1773         oils_i18n_gettext(
1774             'circ.password_reset_request_requires_matching_email',
1775             'Circulation: Require matching email address for password reset requests', 
1776             'coust', 
1777             'label'),
1778         oils_i18n_gettext(
1779             'circ.password_reset_request_requires_matching_email',
1780             'Circulation: Require matching email address for password reset requests', 
1781             'coust', 
1782             'description'),
1783         'bool'
1784 );
1785
1786 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1787         'circ.holds.expired_patron_block',
1788         oils_i18n_gettext(
1789             'circ.holds.expired_patron_block',
1790             'Circulation: Block hold request if hold recipient privileges have expired', 
1791             'coust', 
1792             'label'),
1793         oils_i18n_gettext(
1794             'circ.holds.expired_patron_block',
1795             'Circulation: Block hold request if hold recipient privileges have expired', 
1796             'coust', 
1797             'description'),
1798         'bool'
1799 );
1800
1801 INSERT INTO config.org_unit_setting_type
1802     (name, label, description, datatype) VALUES (
1803         'circ.booking_reservation.default_elbow_room',
1804         oils_i18n_gettext(
1805             'circ.booking_reservation.default_elbow_room',
1806             'Booking: Elbow room',
1807             'coust',
1808             'label'
1809         ),
1810         oils_i18n_gettext(
1811             'circ.booking_reservation.default_elbow_room',
1812             '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.',
1813             'coust',
1814             'label'
1815         ),
1816         'interval'
1817     );
1818
1819 -- Org_unit_setting_type(s) that need an fm_class:
1820 INSERT into config.org_unit_setting_type
1821 ( name, label, description, datatype, fm_class ) VALUES
1822 ( 'acq.default_copy_location',
1823   'Default copy location',
1824   null,
1825   'link',
1826   'acpl' );
1827
1828 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1829     'circ.holds.org_unit_target_weight',
1830     'Holds: Org Unit Target Weight',
1831     '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.',
1832     'integer'
1833 );
1834
1835 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1836     'circ.holds.target_holds_by_org_unit_weight',
1837     'Holds: Use weight-based hold targeting',
1838     'Use library weight based hold targeting',
1839     'bool'
1840 );
1841
1842 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1843     'circ.holds.max_org_unit_target_loops',
1844     'Holds: Maximum library target attempts',
1845     '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',
1846     'integer'
1847 );
1848
1849
1850 -- Org setting for overriding the circ lib of a precat copy
1851 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1852     'circ.pre_cat_copy_circ_lib',
1853     'Pre-cat Item Circ Lib',
1854     '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',
1855     'string'
1856 );
1857
1858 -- Circ auto-renew interval setting
1859 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1860     'circ.checkout_auto_renew_age',
1861     'Checkout auto renew age',
1862     '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',
1863     'interval'
1864 );
1865
1866 -- Setting for behind the desk hold pickups
1867 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1868     'circ.holds.behind_desk_pickup_supported',
1869     'Holds: Behind Desk Pickup Supported',
1870     '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',
1871     'bool'
1872 );
1873
1874 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1875         'acq.holds.allow_holds_from_purchase_request',
1876         oils_i18n_gettext(
1877             'acq.holds.allow_holds_from_purchase_request', 
1878             'Allows patrons to create automatic holds from purchase requests.', 
1879             'coust', 
1880             'label'),
1881         oils_i18n_gettext(
1882             'acq.holds.allow_holds_from_purchase_request', 
1883             'Allows patrons to create automatic holds from purchase requests.', 
1884             'coust', 
1885             'description'),
1886         'bool'
1887 );
1888
1889 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1890     'circ.holds.target_skip_me',
1891     'Skip For Hold Targeting',
1892     'When true, don''t target any copies at this org unit for holds',
1893     'bool'
1894 );
1895
1896 -- claims returned mark item missing 
1897 INSERT INTO
1898     config.org_unit_setting_type ( name, label, description, datatype )
1899     VALUES (
1900         'circ.claim_return.mark_missing',
1901         'Claim Return: Mark copy as missing', 
1902         'When a circ is marked as claims-returned, also mark the copy as missing',
1903         'bool'
1904     );
1905
1906 -- claims never checked out mark item missing 
1907 INSERT INTO
1908     config.org_unit_setting_type ( name, label, description, datatype )
1909     VALUES (
1910         'circ.claim_never_checked_out.mark_missing',
1911         'Claim Never Checked Out: Mark copy as missing', 
1912         'When a circ is marked as claims-never-checked-out, mark the copy as missing',
1913         'bool'
1914     );
1915
1916 -- mark damaged void overdue setting
1917 INSERT INTO
1918     config.org_unit_setting_type ( name, label, description, datatype )
1919     VALUES (
1920         'circ.damaged.void_ovedue',
1921         'Mark item damaged voids overdues',
1922         'When an item is marked damaged, overdue fines on the most recent circulation are voided.',
1923         'bool'
1924     );
1925
1926 -- hold cancel display limits
1927 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1928     VALUES (
1929         'circ.holds.canceled.display_count',
1930         'Holds: Canceled holds display count',
1931         'How many canceled holds to show in patron holds interfaces',
1932         'integer'
1933     );
1934
1935 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1936     VALUES (
1937         'circ.holds.canceled.display_age',
1938         'Holds: Canceled holds display age',
1939         'Show all canceled holds that were canceled within this amount of time',
1940         'interval'
1941     );
1942
1943 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1944     VALUES (
1945         'circ.holds.uncancel.reset_request_time',
1946         'Holds: Reset request time on un-cancel',
1947         'When a hold is uncanceled, reset the request time to push it to the end of the queue',
1948         'bool'
1949     );
1950
1951 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1952     VALUES (
1953         'circ.holds.default_shelf_expire_interval',
1954         'Default hold shelf expire interval',
1955         '',
1956         'interval'
1957 );
1958
1959 INSERT INTO config.org_unit_setting_type (name, label, description, datatype, fm_class)
1960     VALUES (
1961         'circ.claim_return.copy_status', 
1962         'Claim Return Copy Status', 
1963         'Claims returned copies are put into this status.  Default is to leave the copy in the Checked Out status',
1964         'link', 
1965         'ccs' 
1966     );
1967
1968 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
1969     VALUES ( 
1970         'circ.max_fine.cap_at_price',
1971         oils_i18n_gettext('circ.max_fine.cap_at_price', 'Circ: Cap Max Fine at Item Price', 'coust', 'label'),
1972         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'),
1973         'bool' 
1974     );
1975
1976 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) 
1977     VALUES ( 
1978         'circ.holds.clear_shelf.copy_status',
1979         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Holds: Clear shelf copy status', 'coust', 'label'),
1980         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'),
1981         'link',
1982         'ccs'
1983     );
1984
1985 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1986     VALUES ( 
1987         'circ.selfcheck.workstation_required',
1988         oils_i18n_gettext('circ.selfcheck.workstation_required', 'Selfcheck: Workstation Required', 'coust', 'label'),
1989         oils_i18n_gettext('circ.selfcheck.workstation_required', 'All selfcheck stations must use a workstation', 'coust', 'description'),
1990         'bool'
1991     ), (
1992         'circ.selfcheck.patron_password_required',
1993         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Selfcheck: Require Patron Password', 'coust', 'label'),
1994         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Patron must log in with barcode and password at selfcheck station', 'coust', 'description'),
1995         'bool'
1996     );
1997
1998 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1999     VALUES ( 
2000         'circ.selfcheck.alert.sound',
2001         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Selfcheck: Audio Alerts', 'coust', 'label'),
2002         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Use audio alerts for selfcheck events', 'coust', 'description'),
2003         'bool'
2004     );
2005
2006 INSERT INTO
2007     config.org_unit_setting_type (name, label, description, datatype)
2008     VALUES (
2009         'notice.telephony.callfile_lines',
2010         'Telephony: Arbitrary line(s) to include in each notice callfile',
2011         $$
2012         This overrides lines from opensrf.xml.
2013         Line(s) must be valid for your target server and platform
2014         (e.g. Asterisk 1.4).
2015         $$,
2016         'string'
2017     );
2018
2019 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2020     VALUES ( 
2021         'circ.offline.username_allowed',
2022         oils_i18n_gettext('circ.offline.username_allowed', 'Offline: Patron Usernames Allowed', 'coust', 'label'),
2023         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'),
2024         'bool'
2025     );
2026
2027 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2028 VALUES (
2029     'acq.fund.balance_limit.warn',
2030     oils_i18n_gettext('acq.fund.balance_limit.warn', 'Fund Spending Limit for Warning', 'coust', 'label'),
2031     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'),
2032     'integer'
2033 );
2034
2035 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2036 VALUES (
2037     'acq.fund.balance_limit.block',
2038     oils_i18n_gettext('acq.fund.balance_limit.block', 'Fund Spending Limit for Block', 'coust', 'label'),
2039     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'),
2040     'integer'
2041 );
2042
2043 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2044     VALUES (
2045         'circ.holds.hold_has_copy_at.alert',
2046         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'Holds: Has Local Copy Alert', 'coust', 'label'),
2047         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'),
2048         'bool'
2049     ),(
2050         'circ.holds.hold_has_copy_at.block',
2051         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'Holds: Has Local Copy Block', 'coust', 'label'),
2052         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'),
2053         'bool'
2054     );
2055
2056 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2057 VALUES (
2058     'auth.persistent_login_interval',
2059     oils_i18n_gettext('auth.persistent_login_interval', 'Persistent Login Duration', 'coust', 'label'),
2060     oils_i18n_gettext('auth.persistent_login_interval', 'How long a persistent login lasts.  E.g. ''2 weeks''', 'coust', 'description'),
2061     'interval'
2062 );
2063
2064 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2065         'cat.marc_control_number_identifier',
2066         oils_i18n_gettext(
2067             'cat.marc_control_number_identifier', 
2068             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2069             'coust', 
2070             'label'),
2071         oils_i18n_gettext(
2072             'cat.marc_control_number_identifier', 
2073             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2074             'coust', 
2075             'description'),
2076         'string'
2077 );
2078
2079 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) 
2080     VALUES (
2081         'circ.selfcheck.block_checkout_on_copy_status',
2082         oils_i18n_gettext(
2083             'circ.selfcheck.block_checkout_on_copy_status',
2084             'Selfcheck: Block copy checkout status',
2085             'coust',
2086             'label'
2087         ),
2088         oils_i18n_gettext(
2089             'circ.selfcheck.block_checkout_on_copy_status',
2090             'List of copy status IDs that will block checkout even if the generic COPY_NOT_AVAILABLE event is overridden',
2091             'coust',
2092             'description'
2093         ),
2094         'array'
2095     );
2096
2097 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2098 VALUES (
2099     'serial.prev_issuance_copy_location',
2100     oils_i18n_gettext(
2101         'serial.prev_issuance_copy_location',
2102         'Serials: Previous Issuance Copy Location',
2103         'coust',
2104         'label'
2105     ),
2106     oils_i18n_gettext(
2107         'serial.prev_issuance_copy_location',
2108         'When a serial issuance is received, copies (units) of the previous issuance will be automatically moved into the configured shelving location',
2109         'coust',
2110         'descripton'
2111         ),
2112     'link',
2113     'acpl'
2114 );
2115
2116 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2117 VALUES (
2118     'cat.default_classification_scheme',
2119     oils_i18n_gettext(
2120         'cat.default_classification_scheme',
2121         'Cataloging: Default Classification Scheme',
2122         'coust',
2123         'label'
2124     ),
2125     oils_i18n_gettext(
2126         'cat.default_classification_scheme',
2127         'Defines the default classification scheme for new call numbers: 1 = Generic; 2 = Dewey; 3 = LC',
2128         'coust',
2129         'descripton'
2130         ),
2131     'link',
2132     'acnc'
2133 );
2134
2135 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2136         'opac.org_unit_hiding.depth',
2137         oils_i18n_gettext(
2138             'opac.org_unit_hiding.depth',
2139             'OPAC: Org Unit Hiding Depth', 
2140             'coust', 
2141             'label'),
2142         oils_i18n_gettext(
2143             'opac.org_unit_hiding.depth',
2144             '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.', 
2145             'coust', 
2146             'description'),
2147         'integer'
2148 );
2149
2150 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2151     VALUES ( 
2152         'circ.holds.clear_shelf.no_capture_holds',
2153         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'Holds: Bypass hold capture during clear shelf process', 'coust', 'label'),
2154         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'During the clear shelf process, avoid capturing new holds on cleared items.', 'coust', 'description'),
2155         'bool'
2156     );
2157
2158 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
2159     'circ.booking_reservation.stop_circ',
2160     'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
2161     '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.',
2162     'bool'
2163 );
2164
2165 ---------------------------------
2166 -- Seed data for usr_setting_type
2167 ----------------------------------
2168
2169 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2170     VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
2171
2172 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2173     VALUES ('opac.default_search_depth', TRUE, 'OPAC Search Depth', 'OPAC Search Depth', 'integer');
2174
2175 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2176     VALUES ('opac.default_search_location', TRUE, 'OPAC Search Location', 'OPAC Search Location', 'integer');
2177
2178 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2179     VALUES ('opac.hits_per_page', TRUE, 'Hits per Page', 'Hits per Page', 'string');
2180
2181 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2182     VALUES ('opac.hold_notify', TRUE, 'Hold Notification Format', 'Hold Notification Format', 'string');
2183
2184 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2185     VALUES ('staff_client.catalog.record_view.default', TRUE, 'Default Record View', 'Default Record View', 'string');
2186
2187 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2188     VALUES ('staff_client.copy_editor.templates', TRUE, 'Copy Editor Template', 'Copy Editor Template', 'object');
2189
2190 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2191     VALUES ('circ.holds_behind_desk', FALSE, 'Hold is behind Circ Desk', 'Hold is behind Circ Desk', 'bool');
2192
2193 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2194     VALUES (
2195         'history.circ.retention_age',
2196         TRUE,
2197         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','label'),
2198         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','description'),
2199         'interval'
2200     ),(
2201         'history.circ.retention_start',
2202         FALSE,
2203         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','label'),
2204         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','description'),
2205         'date'
2206     );
2207
2208 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2209     VALUES (
2210         'history.hold.retention_age',
2211         TRUE,
2212         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','label'),
2213         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','description'),
2214         'interval'
2215     ),(
2216         'history.hold.retention_start',
2217         TRUE,
2218         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','label'),
2219         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','description'),
2220         'interval'
2221     ),(
2222         'history.hold.retention_count',
2223         TRUE,
2224         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','label'),
2225         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','description'),
2226         'integer'
2227     );
2228
2229 INSERT INTO config.usr_setting_type (name, opac_visible, label, description, datatype)
2230     VALUES (
2231         'opac.default_sort',
2232         TRUE,
2233         oils_i18n_gettext(
2234             'opac.default_sort',
2235             'OPAC Default Search Sort',
2236             'cust',
2237             'label'
2238         ),
2239         oils_i18n_gettext(
2240             'opac.default_sort',
2241             'OPAC Default Search Sort',
2242             'cust',
2243             'description'
2244         ),
2245         'string'
2246     );
2247
2248 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2249         'circ.missing_pieces.copy_status',
2250         oils_i18n_gettext(
2251             'circ.missing_pieces.copy_status',
2252             'Circulation: Item Status for Missing Pieces',
2253             'coust',
2254             'label'),
2255         oils_i18n_gettext(
2256             'circ.missing_pieces.copy_status',
2257             '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.',
2258             'coust',
2259             'description'),
2260         'link',
2261         'ccs'
2262 );
2263
2264 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2265         'circ.do_not_tally_claims_returned',
2266         oils_i18n_gettext(
2267             'circ.do_not_tally_claims_returned',
2268             'Circulation: Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.',
2269             'coust',
2270             'label'),
2271         oils_i18n_gettext(
2272             'circ.do_not_tally_claims_returned',
2273             '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.',
2274             'coust',
2275             'description'),
2276         'bool'
2277 );
2278
2279 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2280     VALUES
2281         ('cat.label.font.size',
2282             oils_i18n_gettext('cat.label.font.size',
2283                 'Cataloging: Spine and pocket label font size', 'coust', 'label'),
2284             oils_i18n_gettext('cat.label.font.size',
2285                 'Set the default font size for spine and pocket labels', 'coust', 'description'),
2286             'integer'
2287         )
2288         ,('cat.label.font.family',
2289             oils_i18n_gettext('cat.label.font.family',
2290                 'Cataloging: Spine and pocket label font family', 'coust', 'label'),
2291             oils_i18n_gettext('cat.label.font.family',
2292                 '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".',
2293                 'coust', 'description'),
2294             'string'
2295         )
2296         ,('cat.spine.line.width',
2297             oils_i18n_gettext('cat.spine.line.width',
2298                 'Cataloging: Spine label line width', 'coust', 'label'),
2299             oils_i18n_gettext('cat.spine.line.width',
2300                 'Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.',
2301                 'coust', 'description'),
2302             'integer'
2303         )
2304         ,('cat.spine.line.height',
2305             oils_i18n_gettext('cat.spine.line.height',
2306                 'Cataloging: Spine label maximum lines', 'coust', 'label'),
2307             oils_i18n_gettext('cat.spine.line.height',
2308                 'Set the default maximum number of lines for spine labels.',
2309                 'coust', 'description'),
2310             'integer'
2311         )
2312         ,('cat.spine.line.margin',
2313             oils_i18n_gettext('cat.spine.line.margin',
2314                 'Cataloging: Spine label left margin', 'coust', 'label'),
2315             oils_i18n_gettext('cat.spine.line.margin',
2316                 'Set the left margin for spine labels in number of characters.',
2317                 'coust', 'description'),
2318             'integer'
2319         )
2320 ;
2321
2322 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2323     VALUES
2324         ('cat.label.font.weight',
2325             oils_i18n_gettext('cat.label.font.weight',
2326                 'Cataloging: Spine and pocket label font weight', 'coust', 'label'),
2327             oils_i18n_gettext('cat.label.font.weight',
2328                 'Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".',
2329                 'coust', 'description'),
2330             'string'
2331         )
2332 ;
2333
2334 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2335         'circ.patron_edit.clone.copy_address',
2336         oils_i18n_gettext(
2337             'circ.patron_edit.clone.copy_address',
2338             'Patron Registration: Cloned patrons get address copy',
2339             'coust',
2340             'label'
2341         ),
2342         oils_i18n_gettext(
2343             'circ.patron_edit.clone.copy_address',
2344             'In the Patron editor, copy addresses from the cloned user instead of linking directly to the address',
2345             'coust',
2346             'description'
2347         ),
2348         'bool'
2349 );
2350
2351 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2352         'ui.patron.default_ident_type',
2353         oils_i18n_gettext(
2354             'ui.patron.default_ident_type',
2355             'GUI: Default Ident Type for Patron Registration',
2356             'coust',
2357             'label'),
2358         oils_i18n_gettext(
2359             'ui.patron.default_ident_type',
2360             'This is the default Ident Type for new users in the patron editor.',
2361             'coust',
2362             'description'),
2363         'link',
2364         'cit'
2365 );
2366
2367 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2368         'ui.patron.default_country',
2369         oils_i18n_gettext(
2370             'ui.patron.default_country',
2371             'GUI: Default Country for New Addresses in Patron Editor',
2372             'coust',
2373             'label'),
2374         oils_i18n_gettext(
2375             'ui.patron.default_country',
2376             'This is the default Country for new addresses in the patron editor.',
2377             'coust',
2378             'description'),
2379         'string'
2380 );
2381
2382 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2383         'ui.patron.registration.require_address',
2384         oils_i18n_gettext(
2385             'ui.patron.registration.require_address',
2386             'GUI: Require at least one address for Patron Registration',
2387             'coust',
2388             'label'),
2389         oils_i18n_gettext(
2390             'ui.patron.registration.require_address',
2391             'Enforces a requirement for having at least one address for a patron during registration.',
2392             'coust',
2393             'description'),
2394         'bool'
2395 );
2396
2397 INSERT INTO config.org_unit_setting_type (
2398     name, label, description, datatype
2399 ) VALUES
2400     ('credit.processor.payflowpro.enabled',
2401         'Credit card processing: Enable PayflowPro payments',
2402         'This is NOT the same thing as the settings labeled with just "PayPal."',
2403         'bool'
2404     ),
2405     ('credit.processor.payflowpro.login',
2406         'Credit card processing: PayflowPro login/merchant ID',
2407         'Often the same thing as the PayPal manager login',
2408         'string'
2409     ),
2410     ('credit.processor.payflowpro.password',
2411         'Credit card processing: PayflowPro password',
2412         'PayflowPro password',
2413         'string'
2414     ),
2415     ('credit.processor.payflowpro.testmode',
2416         'Credit card processing: PayflowPro test mode',
2417         'Do not really process transactions, but stay in test mode - uses pilot-payflowpro.paypal.com instead of the usual host',
2418         'bool'
2419     ),
2420     ('credit.processor.payflowpro.vendor',
2421         'Credit card processing: PayflowPro vendor',
2422         'Often the same thing as the login',
2423         'string'
2424     ),
2425     ('credit.processor.payflowpro.partner',
2426         'Credit card processing: PayflowPro partner',
2427         'Often "PayPal" or "VeriSign", sometimes others',
2428         'string'
2429     );
2430
2431 -- Patch the name of an old selfcheck setting
2432 UPDATE actor.org_unit_setting
2433     SET name = 'circ.selfcheck.alert.popup'
2434     WHERE name = 'circ.selfcheck.alert_on_checkout_event';
2435
2436 -- Rename certain existing org_unit settings, if present,
2437 -- and make sure their values are JSON
2438 UPDATE actor.org_unit_setting SET
2439     name = 'circ.holds.default_estimated_wait_interval',
2440     --
2441     -- The value column should be JSON.  The old value should be a number,
2442     -- but it may or may not be quoted.  The following CASE behaves
2443     -- differently depending on whether value is quoted.  It is simplistic,
2444     -- and will be defeated by leading or trailing white space, or various
2445     -- malformations.
2446     --
2447     value = CASE WHEN SUBSTR( value, 1, 1 ) = '"'
2448                 THEN '"' || SUBSTR( value, 2, LENGTH(value) - 2 ) || ' days"'
2449                 ELSE '"' || value || ' days"'
2450             END
2451 WHERE name = 'circ.hold_estimate_wait_interval';
2452
2453 -- Create types for existing org unit settings
2454 -- not otherwise accounted for
2455
2456 INSERT INTO config.org_unit_setting_type(
2457  name,
2458  label,
2459  description
2460 )
2461 SELECT DISTINCT
2462         name,
2463         name,
2464         'FIXME'
2465 FROM
2466         actor.org_unit_setting
2467 WHERE
2468         name NOT IN (
2469                 SELECT name
2470                 FROM config.org_unit_setting_type
2471         );
2472
2473 -- Add foreign key to org_unit_setting
2474
2475 ALTER TABLE actor.org_unit_setting
2476         ADD FOREIGN KEY (name) REFERENCES config.org_unit_setting_type (name)
2477                 DEFERRABLE INITIALLY DEFERRED;
2478
2479 -- Create types for existing user settings
2480 -- not otherwise accounted for
2481
2482 INSERT INTO config.usr_setting_type (
2483         name,
2484         label,
2485         description
2486 )
2487 SELECT DISTINCT
2488         name,
2489         name,
2490         'FIXME'
2491 FROM
2492         actor.usr_setting
2493 WHERE
2494         name NOT IN (
2495                 SELECT name
2496                 FROM config.usr_setting_type
2497         );
2498
2499 -- Add foreign key to user_setting_type
2500
2501 ALTER TABLE actor.usr_setting
2502         ADD FOREIGN KEY (name) REFERENCES config.usr_setting_type (name)
2503                 ON DELETE CASCADE ON UPDATE CASCADE
2504                 DEFERRABLE INITIALLY DEFERRED;
2505
2506 INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES
2507     (1, 'cat.spine.line.margin', 0)
2508     ,(1, 'cat.spine.line.height', 9)
2509     ,(1, 'cat.spine.line.width', 8)
2510     ,(1, 'cat.label.font.family', '"monospace"')
2511     ,(1, 'cat.label.font.size', 10)
2512     ,(1, 'cat.label.font.weight', '"normal"')
2513 ;
2514
2515 ALTER TABLE action_trigger.event_definition ADD COLUMN granularity TEXT;
2516 ALTER TABLE action_trigger.event ADD COLUMN async_output BIGINT REFERENCES action_trigger.event_output (id);
2517 ALTER TABLE action_trigger.event_definition ADD COLUMN usr_field TEXT;
2518 ALTER TABLE action_trigger.event_definition ADD COLUMN opt_in_setting TEXT REFERENCES config.usr_setting_type (name) DEFERRABLE INITIALLY DEFERRED;
2519
2520 CREATE OR REPLACE FUNCTION is_json( TEXT ) RETURNS BOOL AS $f$
2521     use JSON::XS;
2522     my $json = shift();
2523     eval { JSON::XS->new->allow_nonref->decode( $json ) };
2524     return $@ ? 0 : 1;
2525 $f$ LANGUAGE PLPERLU;
2526
2527 ALTER TABLE action_trigger.event ADD COLUMN user_data TEXT CHECK (user_data IS NULL OR is_json( user_data ));
2528
2529 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2530     'hold_request.cancel.expire_no_target',
2531     'ahr',
2532     'A hold is cancelled because no copies were found'
2533 );
2534
2535 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2536     'hold_request.cancel.expire_holds_shelf',
2537     'ahr',
2538     'A hold is cancelled because it was on the holds shelf too long'
2539 );
2540
2541 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2542     'hold_request.cancel.staff',
2543     'ahr',
2544     'A hold is cancelled because it was cancelled by staff'
2545 );
2546
2547 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2548     'hold_request.cancel.patron',
2549     'ahr',
2550     'A hold is cancelled by the patron'
2551 );
2552
2553 -- Fix typos in descriptions
2554 UPDATE action_trigger.hook SET description = 'A hold is successfully placed' WHERE key = 'hold_request.success';
2555 UPDATE action_trigger.hook SET description = 'A hold is attempted but not successfully placed' WHERE key = 'hold_request.failure';
2556
2557 -- Add a hook for renewals
2558 INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('renewal','circ','Item renewed to user');
2559
2560 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');
2561
2562 -- Sample Pre-due Notice --
2563
2564 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template) 
2565     VALUES (6, 'f', 1, '3 Day Courtesy Notice', 'checkout.due', 'CircIsOpen', 'SendEmail', '-3 days', 'due_date', 'usr', 
2566 $$
2567 [%- USE date -%]
2568 [%- user = target.0.usr -%]
2569 To: [%- params.recipient_email || user.email %]
2570 From: [%- params.sender_email || default_sender %]
2571 Subject: Courtesy Notice
2572
2573 Dear [% user.family_name %], [% user.first_given_name %]
2574 As a reminder, the following items are due in 3 days.
2575
2576 [% FOR circ IN target %]
2577     Title: [% circ.target_copy.call_number.record.simple_record.title %] 
2578     Barcode: [% circ.target_copy.barcode %] 
2579     Due: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2580     Item Cost: [% helpers.get_copy_price(circ.target_copy) %]
2581     Library: [% circ.circ_lib.name %]
2582     Library Phone: [% circ.circ_lib.phone %]
2583 [% END %]
2584
2585 $$);
2586
2587 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2588     (6, 'target_copy.call_number.record.simple_record'),
2589     (6, 'usr'),
2590     (6, 'circ_lib.billing_address');
2591
2592 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2593     (6, 'max_delay_age', '"1 day"');
2594
2595 -- also add the max delay age to the default overdue notice event def
2596 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2597     (1, 'max_delay_age', '"1 day"');
2598   
2599 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');
2600
2601 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.');
2602
2603 INSERT INTO action_trigger.hook (
2604         key,
2605         core_type,
2606         description,
2607         passive
2608     ) VALUES (
2609         'hold_request.shelf_expires_soon',
2610         'ahr',
2611         'A hold on the shelf will expire there soon.',
2612         TRUE
2613     );
2614
2615 INSERT INTO action_trigger.event_definition (
2616         id,
2617         active,
2618         owner,
2619         name,
2620         hook,
2621         validator,
2622         reactor,
2623         delay,
2624         delay_field,
2625         group_field,
2626         template
2627     ) VALUES (
2628         7,
2629         FALSE,
2630         1,
2631         'Hold Expires from Shelf Soon',
2632         'hold_request.shelf_expires_soon',
2633         'HoldIsAvailable',
2634         'SendEmail',
2635         '- 1 DAY',
2636         'shelf_expire_time',
2637         'usr',
2638 $$
2639 [%- USE date -%]
2640 [%- user = target.0.usr -%]
2641 To: [%- params.recipient_email || user.email %]
2642 From: [%- params.sender_email || default_sender %]
2643 Subject: Hold Available Notification
2644
2645 Dear [% user.family_name %], [% user.first_given_name %]
2646 You requested holds on the following item(s), which are available for
2647 pickup, but these holds will soon expire.
2648
2649 [% FOR hold IN target %]
2650     [%- data = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
2651     Title: [% data.title %]
2652     Author: [% data.author %]
2653     Library: [% hold.pickup_lib.name %]
2654 [% END %]
2655 $$
2656     );
2657
2658 INSERT INTO action_trigger.environment (
2659         event_def,
2660         path
2661     ) VALUES
2662     ( 7, 'current_copy'),
2663     ( 7, 'pickup_lib.billing_address'),
2664     ( 7, 'usr');
2665
2666 INSERT INTO action_trigger.hook (
2667         key,
2668         core_type,
2669         description,
2670         passive
2671     ) VALUES (
2672         'hold_request.long_wait',
2673         'ahr',
2674         'A patron has been waiting on a hold to be fulfilled for a long time.',
2675         TRUE
2676     );
2677
2678 INSERT INTO action_trigger.event_definition (
2679         id,
2680         active,
2681         owner,
2682         name,
2683         hook,
2684         validator,
2685         reactor,
2686         delay,
2687         delay_field,
2688         group_field,
2689         template
2690     ) VALUES (
2691         9,
2692         FALSE,
2693         1,
2694         'Hold waiting for pickup for long time',
2695         'hold_request.long_wait',
2696         'NOOP_True',
2697         'SendEmail',
2698         '6 MONTHS',
2699         'request_time',
2700         'usr',
2701 $$
2702 [%- USE date -%]
2703 [%- user = target.0.usr -%]
2704 To: [%- params.recipient_email || user.email %]
2705 From: [%- params.sender_email || default_sender %]
2706 Subject: Long Wait Hold Notification
2707
2708 Dear [% user.family_name %], [% user.first_given_name %]
2709
2710 You requested hold(s) on the following item(s), but unfortunately
2711 we have not been able to fulfill your request after a considerable
2712 length of time.  If you would still like to receive these items,
2713 no action is required.
2714
2715 [% FOR hold IN target %]
2716     Title: [% hold.bib_rec.bib_record.simple_record.title %]
2717     Author: [% hold.bib_rec.bib_record.simple_record.author %]
2718 [% END %]
2719 $$
2720 );
2721
2722 INSERT INTO action_trigger.environment (
2723         event_def,
2724         path
2725     ) VALUES
2726     (9, 'pickup_lib'),
2727     (9, 'usr'),
2728     (9, 'bib_rec.bib_record.simple_record');
2729
2730 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2731     VALUES (
2732         'format.selfcheck.checkout',
2733         'circ',
2734         'Formats circ objects for self-checkout receipt',
2735         TRUE
2736     );
2737
2738 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2739     VALUES (
2740         10,
2741         TRUE,
2742         1,
2743         'Self-Checkout Receipt',
2744         'format.selfcheck.checkout',
2745         'NOOP_True',
2746         'ProcessTemplate',
2747         'usr',
2748         'print-on-demand',
2749 $$
2750 [%- USE date -%]
2751 [%- SET user = target.0.usr -%]
2752 [%- SET lib = target.0.circ_lib -%]
2753 [%- SET lib_addr = target.0.circ_lib.billing_address -%]
2754 [%- SET hours = lib.hours_of_operation -%]
2755 <div>
2756     <style> li { padding: 8px; margin 5px; }</style>
2757     <div>[% date.format %]</div>
2758     <div>[% lib.name %]</div>
2759     <div>[% lib_addr.street1 %] [% lib_addr.street2 %]</div>
2760     <div>[% lib_addr.city %], [% lib_addr.state %] [% lb_addr.post_code %]</div>
2761     <div>[% lib.phone %]</div>
2762     <br/>
2763
2764     [% user.family_name %], [% user.first_given_name %]
2765     <ol>
2766     [% FOR circ IN target %]
2767         [%-
2768             SET idx = loop.count - 1;
2769             SET udata =  user_data.$idx
2770         -%]
2771         <li>
2772             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2773             <div>Barcode: [% circ.target_copy.barcode %]</div>
2774             [% IF udata.renewal_failure %]
2775                 <div style='color:red;'>Renewal Failed</div>
2776             [% ELSE %]
2777                 <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2778             [% END %]
2779         </li>
2780     [% END %]
2781     </ol>
2782     
2783     <div>
2784         Library Hours
2785         [%- BLOCK format_time; date.format(time _ ' 1/1/1000', format='%I:%M %p'); END -%]
2786         <div>
2787             Monday 
2788             [% PROCESS format_time time = hours.dow_0_open %] 
2789             [% PROCESS format_time time = hours.dow_0_close %] 
2790         </div>
2791         <div>
2792             Tuesday 
2793             [% PROCESS format_time time = hours.dow_1_open %] 
2794             [% PROCESS format_time time = hours.dow_1_close %] 
2795         </div>
2796         <div>
2797             Wednesday 
2798             [% PROCESS format_time time = hours.dow_2_open %] 
2799             [% PROCESS format_time time = hours.dow_2_close %] 
2800         </div>
2801         <div>
2802             Thursday
2803             [% PROCESS format_time time = hours.dow_3_open %] 
2804             [% PROCESS format_time time = hours.dow_3_close %] 
2805         </div>
2806         <div>
2807             Friday
2808             [% PROCESS format_time time = hours.dow_4_open %] 
2809             [% PROCESS format_time time = hours.dow_4_close %] 
2810         </div>
2811         <div>
2812             Saturday
2813             [% PROCESS format_time time = hours.dow_5_open %] 
2814             [% PROCESS format_time time = hours.dow_5_close %] 
2815         </div>
2816         <div>
2817             Sunday 
2818             [% PROCESS format_time time = hours.dow_6_open %] 
2819             [% PROCESS format_time time = hours.dow_6_close %] 
2820         </div>
2821     </div>
2822 </div>
2823 $$
2824 );
2825
2826 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2827     ( 10, 'target_copy'),
2828     ( 10, 'circ_lib.billing_address'),
2829     ( 10, 'circ_lib.hours_of_operation'),
2830     ( 10, 'usr');
2831
2832 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2833     VALUES (
2834         'format.selfcheck.items_out',
2835         'circ',
2836         'Formats items out for self-checkout receipt',
2837         TRUE
2838     );
2839
2840 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2841     VALUES (
2842         11,
2843         TRUE,
2844         1,
2845         'Self-Checkout Items Out Receipt',
2846         'format.selfcheck.items_out',
2847         'NOOP_True',
2848         'ProcessTemplate',
2849         'usr',
2850         'print-on-demand',
2851 $$
2852 [%- USE date -%]
2853 [%- SET user = target.0.usr -%]
2854 <div>
2855     <style> li { padding: 8px; margin 5px; }</style>
2856     <div>[% date.format %]</div>
2857     <br/>
2858
2859     [% user.family_name %], [% user.first_given_name %]
2860     <ol>
2861     [% FOR circ IN target %]
2862         <li>
2863             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2864             <div>Barcode: [% circ.target_copy.barcode %]</div>
2865             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2866         </li>
2867     [% END %]
2868     </ol>
2869 </div>
2870 $$
2871 );
2872
2873
2874 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2875     ( 11, 'target_copy'),
2876     ( 11, 'circ_lib.billing_address'),
2877     ( 11, 'circ_lib.hours_of_operation'),
2878     ( 11, 'usr');
2879
2880 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2881     VALUES (
2882         'format.selfcheck.holds',
2883         'ahr',
2884         'Formats holds for self-checkout receipt',
2885         TRUE
2886     );
2887
2888 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2889     VALUES (
2890         12,
2891         TRUE,
2892         1,
2893         'Self-Checkout Holds Receipt',
2894         'format.selfcheck.holds',
2895         'NOOP_True',
2896         'ProcessTemplate',
2897         'usr',
2898         'print-on-demand',
2899 $$
2900 [%- USE date -%]
2901 [%- SET user = target.0.usr -%]
2902 <div>
2903     <style> li { padding: 8px; margin 5px; }</style>
2904     <div>[% date.format %]</div>
2905     <br/>
2906
2907     [% user.family_name %], [% user.first_given_name %]
2908     <ol>
2909     [% FOR hold IN target %]
2910         [%-
2911             SET idx = loop.count - 1;
2912             SET udata =  user_data.$idx
2913         -%]
2914         <li>
2915             <div>Title: [% hold.bib_rec.bib_record.simple_record.title %]</div>
2916             <div>Author: [% hold.bib_rec.bib_record.simple_record.author %]</div>
2917             <div>Pickup Location: [% hold.pickup_lib.name %]</div>
2918             <div>Status: 
2919                 [%- IF udata.ready -%]
2920                     Ready for pickup
2921                 [% ELSE %]
2922                     #[% udata.queue_position %] of [% udata.potential_copies %] copies.
2923                 [% END %]
2924             </div>
2925         </li>
2926     [% END %]
2927     </ol>
2928 </div>
2929 $$
2930 );
2931
2932
2933 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2934     ( 12, 'bib_rec.bib_record.simple_record'),
2935     ( 12, 'pickup_lib'),
2936     ( 12, 'usr');
2937
2938 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2939     VALUES (
2940         'format.selfcheck.fines',
2941         'au',
2942         'Formats fines for self-checkout receipt',
2943         TRUE
2944     );
2945
2946 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template )
2947     VALUES (
2948         13,
2949         TRUE,
2950         1,
2951         'Self-Checkout Fines Receipt',
2952         'format.selfcheck.fines',
2953         'NOOP_True',
2954         'ProcessTemplate',
2955         'print-on-demand',
2956 $$
2957 [%- USE date -%]
2958 [%- SET user = target -%]
2959 <div>
2960     <style> li { padding: 8px; margin 5px; }</style>
2961     <div>[% date.format %]</div>
2962     <br/>
2963
2964     [% user.family_name %], [% user.first_given_name %]
2965     <ol>
2966     [% FOR xact IN user.open_billable_transactions_summary %]
2967         <li>
2968             <div>Details: 
2969                 [% IF xact.xact_type == 'circulation' %]
2970                     [%- helpers.get_copy_bib_basics(xact.circulation.target_copy).title -%]
2971                 [% ELSE %]
2972                     [%- xact.last_billing_type -%]
2973                 [% END %]
2974             </div>
2975             <div>Total Billed: [% xact.total_owed %]</div>
2976             <div>Total Paid: [% xact.total_paid %]</div>
2977             <div>Balance Owed : [% xact.balance_owed %]</div>
2978         </li>
2979     [% END %]
2980     </ol>
2981 </div>
2982 $$
2983 );
2984
2985 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2986     ( 13, 'open_billable_transactions_summary.circulation' );
2987
2988 INSERT INTO action_trigger.reactor (module,description) VALUES
2989 (   'SendFile',
2990     oils_i18n_gettext(
2991         'SendFile',
2992         '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.',
2993         'atreact',
2994         'description'
2995     )
2996 );
2997
2998 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2999     VALUES (
3000         'format.acqli.html',
3001         'jub',
3002         'Formats lineitem worksheet for titles received',
3003         TRUE
3004     );
3005
3006 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template)
3007     VALUES (
3008         14,
3009         TRUE,
3010         1,
3011         'Lineitem Worksheet',
3012         'format.acqli.html',
3013         'NOOP_True',
3014         'ProcessTemplate',
3015         'print-on-demand',
3016 $$
3017 [%- USE date -%]
3018 [%- SET li = target; -%]
3019 <div class="wrapper">
3020     <div class="summary" style='font-size:110%; font-weight:bold;'>
3021
3022         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
3023         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
3024         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
3025         <div class="lineid">Lineitem ID: [% li.id %]</div>
3026
3027         [% IF li.distribution_formulas.size > 0 %]
3028             [% SET forms = [] %]
3029             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
3030             <div>Distribution Formulas: [% forms.join(',') %]</div>
3031         [% END %]
3032
3033         [% IF li.lineitem_notes.size > 0 %]
3034             Lineitem Notes:
3035             <ul>
3036                 [%- FOR note IN li.lineitem_notes -%]
3037                     <li>
3038                     [% IF note.alert_text %]
3039                         [% note.alert_text.code -%] 
3040                         [% IF note.value -%]
3041                             : [% note.value %]
3042                         [% END %]
3043                     [% ELSE %]
3044                         [% note.value -%] 
3045                     [% END %]
3046                     </li>
3047                 [% END %]
3048             </ul>
3049         [% END %]
3050     </div>
3051     <br/>
3052     <table>
3053         <thead>
3054             <tr>
3055                 <th>Branch</th>
3056                 <th>Barcode</th>
3057                 <th>Call Number</th>
3058                 <th>Fund</th>
3059                 <th>Recd.</th>
3060                 <th>Notes</th>
3061             </tr>
3062         </thead>
3063         <tbody>
3064         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
3065             [% 
3066                 IF copy.eg_copy_id;
3067                     SET copy = copy.eg_copy_id;
3068                     SET cn_label = copy.call_number.label;
3069                 ELSE; 
3070                     SET copy = detail; 
3071                     SET cn_label = detail.cn_label;
3072                 END 
3073             %]
3074             <tr>
3075                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
3076                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
3077                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
3078                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
3079                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
3080                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
3081                 <td style='padding:5px;'>[% detail.note %]</td>
3082             </tr>
3083         [% END %]
3084         </tbody>
3085     </table>
3086 </div>
3087 $$
3088 );
3089
3090
3091 INSERT INTO action_trigger.environment (event_def, path) VALUES
3092     ( 14, 'attributes' ),
3093     ( 14, 'lineitem_details' ),
3094     ( 14, 'lineitem_details.owning_lib' ),
3095     ( 14, 'lineitem_notes' )
3096 ;
3097
3098 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3099         'aur.ordered',
3100         'aur', 
3101         oils_i18n_gettext(
3102             'aur.ordered',
3103             'A patron acquisition request has been marked On-Order.',
3104             'ath',
3105             'description'
3106         ), 
3107         TRUE
3108     ), (
3109         'aur.received', 
3110         'aur', 
3111         oils_i18n_gettext(
3112             'aur.received', 
3113             'A patron acquisition request has been marked Received.',
3114             'ath',
3115             'description'
3116         ),
3117         TRUE
3118     ), (
3119         'aur.cancelled',
3120         'aur',
3121         oils_i18n_gettext(
3122             'aur.cancelled',
3123             'A patron acquisition request has been marked Cancelled.',
3124             'ath',
3125             'description'
3126         ),
3127         TRUE
3128     )
3129 ;
3130
3131 INSERT INTO action_trigger.validator (module,description) VALUES (
3132         'Acq::UserRequestOrdered',
3133         oils_i18n_gettext(
3134             'Acq::UserRequestOrdered',
3135             'Tests to see if the corresponding Line Item has a state of "on-order".',
3136             'atval',
3137             'description'
3138         )
3139     ), (
3140         'Acq::UserRequestReceived',
3141         oils_i18n_gettext(
3142             'Acq::UserRequestReceived',
3143             'Tests to see if the corresponding Line Item has a state of "received".',
3144             'atval',
3145             'description'
3146         )
3147     ), (
3148         'Acq::UserRequestCancelled',
3149         oils_i18n_gettext(
3150             'Acq::UserRequestCancelled',
3151             'Tests to see if the corresponding Line Item has a state of "cancelled".',
3152             'atval',
3153             'description'
3154         )
3155     )
3156 ;
3157
3158 -- What was event_definition #15 in v1.6.1 will be recreated as #20.  This
3159 -- renumbering requires some juggling:
3160 --
3161 -- 1. Update any child rows to point to #20.  These updates will temporarily
3162 -- violate foreign key constraints, but that's okay as long as we create
3163 -- #20 before committing.
3164 --
3165 -- 2. Delete the old #15.
3166 --
3167 -- 3. Insert the new #15.
3168 --
3169 -- 4. Insert #20.
3170 --
3171 -- We could combine steps 2 and 3 into a single update, but that would create
3172 -- additional opportunities for typos, since we already have the insert from
3173 -- an upgrade script.
3174
3175 UPDATE action_trigger.environment
3176 SET event_def = 20
3177 WHERE event_def = 15;
3178
3179 UPDATE action_trigger.event
3180 SET event_def = 20
3181 WHERE event_def = 15;
3182
3183 UPDATE action_trigger.event_params
3184 SET event_def = 20
3185 WHERE event_def = 15;
3186
3187 DELETE FROM action_trigger.event_definition
3188 WHERE id = 15;
3189
3190 INSERT INTO action_trigger.event_definition (
3191         id,
3192         active,
3193         owner,
3194         name,
3195         hook,
3196         validator,
3197         reactor,
3198         template
3199     ) VALUES (
3200         15,
3201         FALSE,
3202         1,
3203         'Email Notice: Patron Acquisition Request marked On-Order.',
3204         'aur.ordered',
3205         'Acq::UserRequestOrdered',
3206         'SendEmail',
3207 $$
3208 [%- USE date -%]
3209 [%- SET li = target.lineitem; -%]
3210 [%- SET user = target.usr -%]
3211 [%- SET title = helpers.get_li_attr("title", "", li.attributes) -%]
3212 [%- SET author = helpers.get_li_attr("author", "", li.attributes) -%]
3213 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) -%]
3214 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) -%]
3215 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3216 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3217
3218 To: [%- params.recipient_email || user.email %]
3219 From: [%- params.sender_email || default_sender %]
3220 Subject: Acquisition Request Notification
3221
3222 Dear [% user.family_name %], [% user.first_given_name %]
3223 Our records indicate the following acquisition request has been placed on order.
3224
3225 Title: [% title %]
3226 [% IF author %]Author: [% author %][% END %]
3227 [% IF edition %]Edition: [% edition %][% END %]
3228 [% IF isbn %]ISBN: [% isbn %][% END %]
3229 [% IF publisher %]Publisher: [% publisher %][% END %]
3230 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3231 Lineitem ID: [% li.id %]
3232 $$
3233     ), (
3234         16,
3235         FALSE,
3236         1,
3237         'Email Notice: Patron Acquisition Request marked Received.',
3238         'aur.received',
3239         'Acq::UserRequestReceived',
3240         'SendEmail',
3241 $$
3242 [%- USE date -%]
3243 [%- SET li = target.lineitem; -%]
3244 [%- SET user = target.usr -%]
3245 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3246 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3247 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3248 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3249 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3250 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3251
3252 To: [%- params.recipient_email || user.email %]
3253 From: [%- params.sender_email || default_sender %]
3254 Subject: Acquisition Request Notification
3255
3256 Dear [% user.family_name %], [% user.first_given_name %]
3257 Our records indicate the materials for the following acquisition request have been received.
3258
3259 Title: [% title %]
3260 [% IF author %]Author: [% author %][% END %]
3261 [% IF edition %]Edition: [% edition %][% END %]
3262 [% IF isbn %]ISBN: [% isbn %][% END %]
3263 [% IF publisher %]Publisher: [% publisher %][% END %]
3264 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3265 Lineitem ID: [% li.id %]
3266 $$
3267     ), (
3268         17,
3269         FALSE,
3270         1,
3271         'Email Notice: Patron Acquisition Request marked Cancelled.',
3272         'aur.cancelled',
3273         'Acq::UserRequestCancelled',
3274         'SendEmail',
3275 $$
3276 [%- USE date -%]
3277 [%- SET li = target.lineitem; -%]
3278 [%- SET user = target.usr -%]
3279 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3280 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3281 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3282 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3283 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3284 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3285
3286 To: [%- params.recipient_email || user.email %]
3287 From: [%- params.sender_email || default_sender %]
3288 Subject: Acquisition Request Notification
3289
3290 Dear [% user.family_name %], [% user.first_given_name %]
3291 Our records indicate the following acquisition request has been cancelled.
3292
3293 Title: [% title %]
3294 [% IF author %]Author: [% author %][% END %]
3295 [% IF edition %]Edition: [% edition %][% END %]
3296 [% IF isbn %]ISBN: [% isbn %][% END %]
3297 [% IF publisher %]Publisher: [% publisher %][% END %]
3298 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3299 Lineitem ID: [% li.id %]
3300 $$
3301     );
3302
3303 INSERT INTO action_trigger.environment (
3304         event_def,
3305         path
3306     ) VALUES 
3307         ( 15, 'lineitem' ),
3308         ( 15, 'lineitem.attributes' ),
3309         ( 15, 'usr' ),
3310
3311         ( 16, 'lineitem' ),
3312         ( 16, 'lineitem.attributes' ),
3313         ( 16, 'usr' ),
3314
3315         ( 17, 'lineitem' ),
3316         ( 17, 'lineitem.attributes' ),
3317         ( 17, 'usr' )
3318     ;
3319
3320 INSERT INTO action_trigger.event_definition
3321 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
3322 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
3323 $$
3324 [%- USE date -%]
3325 [%# start JEDI document 
3326   # Vendor specific kludges:
3327   # BT      - vendcode goes to NAD/BY *suffix*  w/ 91 qualifier
3328   # INGRAM  - vendcode goes to NAD/BY *segment* w/ 91 qualifier (separately)
3329   # BRODART - vendcode goes to FTX segment (lineitem level)
3330 -%]
3331 [%- 
3332 IF target.provider.edi_default.vendcode && target.provider.code == 'BRODART';
3333     xtra_ftx = target.provider.edi_default.vendcode;
3334 END;
3335 -%]
3336 [%- BLOCK big_block -%]
3337 {
3338    "recipient":"[% target.provider.san %]",
3339    "sender":"[% target.ordering_agency.mailing_address.san %]",
3340    "body": [{
3341      "ORDERS":[ "order", {
3342         "po_number":[% target.id %],
3343         "date":"[% date.format(date.now, '%Y%m%d') %]",
3344         "buyer":[
3345             [%   IF   target.provider.edi_default.vendcode && (target.provider.code == 'BT' || target.provider.name.match('(?i)^BAKER & TAYLOR'))  -%]
3346                 {"id-qualifier": 91, "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]"}
3347             [%- ELSIF target.provider.edi_default.vendcode && target.provider.code == 'INGRAM' -%]
3348                 {"id":"[% target.ordering_agency.mailing_address.san %]"},
3349                 {"id-qualifier": 91, "id":"[% target.provider.edi_default.vendcode %]"}
3350             [%- ELSE -%]
3351                 {"id":"[% target.ordering_agency.mailing_address.san %]"}
3352             [%- END -%]
3353         ],
3354         "vendor":[
3355             [%- # target.provider.name (target.provider.id) -%]
3356             "[% target.provider.san %]",
3357             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
3358         ],
3359         "currency":"[% target.provider.currency_type %]",
3360                 
3361         "items":[
3362         [%- FOR li IN target.lineitems %]
3363         {
3364             "line_index":"[% li.id %]",
3365             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
3366             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
3367                 [% IF isbn.length == 13 -%]
3368                 {"id-qualifier":"EN","id":"[% isbn %]"},
3369                 [% ELSE -%]
3370                 {"id-qualifier":"IB","id":"[% isbn %]"},
3371                 [%- END %]
3372             [% END %]
3373                 {"id-qualifier":"IN","id":"[% li.id %]"}
3374             ],
3375             "price":[% li.estimated_unit_price || '0.00' %],
3376             "desc":[
3377                 {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"},
3378                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
3379                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
3380                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
3381             ],
3382             [%- ftx_vals = []; 
3383                 FOR note IN li.lineitem_notes; 
3384                     NEXT UNLESS note.vendor_public == 't'; 
3385                     ftx_vals.push(note.value); 
3386                 END; 
3387                 IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
3388                 IF ftx_vals.size == 0; ftx_vals.unshift('');       END;  # BT needs FTX+LIN for every LI, even if it is an empty one
3389             -%]
3390
3391             "free-text":[ 
3392                 [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
3393             ],            
3394             "quantity":[% li.lineitem_details.size %]
3395         }[% UNLESS loop.last %],[% END %]
3396         [%-# TODO: lineitem details (later) -%]
3397         [% END %]
3398         ],
3399         "line_items":[% target.lineitems.size %]
3400      }]  [%# close ORDERS array %]
3401    }]    [%# close  body  array %]
3402 }
3403 [% END %]
3404 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
3405
3406 $$);
3407
3408 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3409   (23, 'lineitems.attributes'), 
3410   (23, 'lineitems.lineitem_details'), 
3411   (23, 'lineitems.lineitem_notes'), 
3412   (23, 'ordering_agency.mailing_address'), 
3413   (23, 'provider');
3414
3415 UPDATE action_trigger.event_definition SET template = 
3416 $$
3417 [%- USE date -%]
3418 [%-
3419     # find a lineitem attribute by name and optional type
3420     BLOCK get_li_attr;
3421         FOR attr IN li.attributes;
3422             IF attr.attr_name == attr_name;
3423                 IF !attr_type OR attr_type == attr.attr_type;
3424                     attr.attr_value;
3425                     LAST;
3426                 END;
3427             END;
3428         END;
3429     END
3430 -%]
3431
3432 <h2>Purchase Order [% target.id %]</h2>
3433 <br/>
3434 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
3435 <br/>
3436
3437 <style>
3438     table td { padding:5px; border:1px solid #aaa;}
3439     table { width:95%; border-collapse:collapse; }
3440     #vendor-notes { padding:5px; border:1px solid #aaa; }
3441 </style>
3442 <table id='vendor-table'>
3443   <tr>
3444     <td valign='top'>Vendor</td>
3445     <td>
3446       <div>[% target.provider.name %]</div>
3447       <div>[% target.provider.addresses.0.street1 %]</div>
3448       <div>[% target.provider.addresses.0.street2 %]</div>
3449       <div>[% target.provider.addresses.0.city %]</div>
3450       <div>[% target.provider.addresses.0.state %]</div>
3451       <div>[% target.provider.addresses.0.country %]</div>
3452       <div>[% target.provider.addresses.0.post_code %]</div>
3453     </td>
3454     <td valign='top'>Ship to / Bill to</td>
3455     <td>
3456       <div>[% target.ordering_agency.name %]</div>
3457       <div>[% target.ordering_agency.billing_address.street1 %]</div>
3458       <div>[% target.ordering_agency.billing_address.street2 %]</div>
3459       <div>[% target.ordering_agency.billing_address.city %]</div>
3460       <div>[% target.ordering_agency.billing_address.state %]</div>
3461       <div>[% target.ordering_agency.billing_address.country %]</div>
3462       <div>[% target.ordering_agency.billing_address.post_code %]</div>
3463     </td>
3464   </tr>
3465 </table>
3466
3467 <br/><br/>
3468 <fieldset id='vendor-notes'>
3469     <legend>Notes to the Vendor</legend>
3470     <ul>
3471     [% FOR note IN target.notes %]
3472         [% IF note.vendor_public == 't' %]
3473             <li>[% note.value %]</li>
3474         [% END %]
3475     [% END %]
3476     </ul>
3477 </fieldset>
3478 <br/><br/>
3479
3480 <table>
3481   <thead>
3482     <tr>
3483       <th>PO#</th>
3484       <th>ISBN or Item #</th>
3485       <th>Title</th>
3486       <th>Quantity</th>
3487       <th>Unit Price</th>
3488       <th>Line Total</th>
3489       <th>Notes</th>
3490     </tr>
3491   </thead>
3492   <tbody>
3493
3494   [% subtotal = 0 %]
3495   [% FOR li IN target.lineitems %]
3496
3497   <tr>
3498     [% count = li.lineitem_details.size %]
3499     [% price = li.estimated_unit_price %]
3500     [% litotal = (price * count) %]
3501     [% subtotal = subtotal + litotal %]
3502     [% isbn = PROCESS get_li_attr attr_name = 'isbn' %]
3503     [% ident = PROCESS get_li_attr attr_name = 'identifier' %]
3504
3505     <td>[% target.id %]</td>
3506     <td>[% isbn || ident %]</td>
3507     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
3508     <td>[% count %]</td>
3509     <td>[% price %]</td>
3510     <td>[% litotal %]</td>
3511     <td>
3512         <ul>
3513         [% FOR note IN li.lineitem_notes %]
3514             [% IF note.vendor_public == 't' %]
3515                 <li>[% note.value %]</li>
3516             [% END %]
3517         [% END %]
3518         </ul>
3519     </td>
3520   </tr>
3521   [% END %]
3522   <tr>
3523     <td/><td/><td/><td/>
3524     <td>Subtotal</td>
3525     <td>[% subtotal %]</td>
3526   </tr>
3527   </tbody>
3528 </table>
3529
3530 <br/>
3531
3532 Total Line Item Count: [% target.lineitems.size %]
3533 $$
3534 WHERE id = 4;
3535
3536 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3537     (4, 'lineitems.lineitem_notes'),
3538     (4, 'notes');
3539
3540 INSERT INTO action_trigger.environment (event_def, path) VALUES
3541     ( 14, 'lineitem_notes.alert_text' ),
3542     ( 14, 'distribution_formulas.formula' ),
3543     ( 14, 'lineitem_details.fund' ),
3544     ( 14, 'lineitem_details.eg_copy_id' ),
3545     ( 14, 'lineitem_details.eg_copy_id.call_number' )
3546 ;
3547
3548 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3549         'aur.created',
3550         'aur',
3551         oils_i18n_gettext(
3552             'aur.created',
3553             'A patron has made an acquisitions request.',
3554             'ath',
3555             'description'
3556         ),
3557         TRUE
3558     ), (
3559         'aur.rejected',
3560         'aur',
3561         oils_i18n_gettext(
3562             'aur.rejected',
3563             'A patron acquisition request has been rejected.',
3564             'ath',
3565             'description'
3566         ),
3567         TRUE
3568     )
3569 ;
3570
3571 INSERT INTO action_trigger.event_definition (
3572         id,
3573         active,
3574         owner,
3575         name,
3576         hook,
3577         validator,
3578         reactor,
3579         template
3580     ) VALUES (
3581         18,
3582         FALSE,
3583         1,
3584         'Email Notice: Acquisition Request created.',
3585         'aur.created',
3586         'NOOP_True',
3587         'SendEmail',
3588 $$
3589 [%- USE date -%]
3590 [%- SET user = target.usr -%]
3591 [%- SET title = target.title -%]
3592 [%- SET author = target.author -%]
3593 [%- SET isxn = target.isxn -%]
3594 [%- SET publisher = target.publisher -%]
3595 [%- SET pubdate = target.pubdate -%]
3596
3597 To: [%- params.recipient_email || user.email %]
3598 From: [%- params.sender_email || default_sender %]
3599 Subject: Acquisition Request Notification
3600
3601 Dear [% user.family_name %], [% user.first_given_name %]
3602 Our records indicate that you have made the following acquisition request:
3603
3604 Title: [% title %]
3605 [% IF author %]Author: [% author %][% END %]
3606 [% IF edition %]Edition: [% edition %][% END %]
3607 [% IF isbn %]ISXN: [% isxn %][% END %]
3608 [% IF publisher %]Publisher: [% publisher %][% END %]
3609 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3610 $$
3611     ), (
3612         19,
3613         FALSE,
3614         1,
3615         'Email Notice: Acquisition Request Rejected.',
3616         'aur.rejected',
3617         'NOOP_True',
3618         'SendEmail',
3619 $$
3620 [%- USE date -%]
3621 [%- SET user = target.usr -%]
3622 [%- SET title = target.title -%]
3623 [%- SET author = target.author -%]
3624 [%- SET isxn = target.isxn -%]
3625 [%- SET publisher = target.publisher -%]
3626 [%- SET pubdate = target.pubdate -%]
3627 [%- SET cancel_reason = target.cancel_reason.description -%]
3628
3629 To: [%- params.recipient_email || user.email %]
3630 From: [%- params.sender_email || default_sender %]
3631 Subject: Acquisition Request Notification
3632
3633 Dear [% user.family_name %], [% user.first_given_name %]
3634 Our records indicate the following acquisition request has been rejected for this reason: [% cancel_reason %]
3635
3636 Title: [% title %]
3637 [% IF author %]Author: [% author %][% END %]
3638 [% IF edition %]Edition: [% edition %][% END %]
3639 [% IF isbn %]ISBN: [% isbn %][% END %]
3640 [% IF publisher %]Publisher: [% publisher %][% END %]
3641 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3642 $$
3643     );
3644
3645 INSERT INTO action_trigger.environment (
3646         event_def,
3647         path
3648     ) VALUES 
3649         ( 18, 'usr' ),
3650         ( 19, 'usr' ),
3651         ( 19, 'cancel_reason' )
3652     ;
3653
3654 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, template) 
3655     VALUES (20, 'f', 1, 'Password reset request notification', 'password.reset_request', 'NOOP_True', 'SendEmail', '00:00:01',
3656 $$
3657 [%- USE date -%]
3658 [%- user = target.usr -%]
3659 To: [%- params.recipient_email || user.email %]
3660 From: [%- params.sender_email || user.home_ou.email || default_sender %]
3661 Subject: [% user.home_ou.name %]: library account password reset request
3662   
3663 You have received this message because you, or somebody else, requested a reset
3664 of your library system password. If you did not request a reset of your library
3665 system password, just ignore this message and your current password will
3666 continue to work.
3667
3668 If you did request a reset of your library system password, please perform
3669 the following steps to continue the process of resetting your password:
3670
3671 1. Open the following link in a web browser: https://[% params.hostname %]/opac/password/[% params.locale || 'en-US' %]/[% target.uuid %]
3672 The browser displays a password reset form.
3673
3674 2. Enter your new password in the password reset form in the browser. You must
3675 enter the password twice to ensure that you do not make a mistake. If the
3676 passwords match, you will then be able to log in to your library system account
3677 with the new password.
3678
3679 $$);
3680
3681 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3682     VALUES (
3683         'format.acqcle.html',
3684         'acqcle',
3685         'Formats claim events into a voucher',
3686         TRUE
3687     );
3688
3689 INSERT INTO action_trigger.event_definition (
3690         id, active, owner, name, hook, group_field,
3691         validator, reactor, granularity, template
3692     ) VALUES (
3693         21,
3694         TRUE,
3695         1,
3696         'Claim Voucher',
3697         'format.acqcle.html',
3698         'claim',
3699         'NOOP_True',
3700         'ProcessTemplate',
3701         'print-on-demand',
3702 $$
3703 [%- USE date -%]
3704 [%- SET claim = target.0.claim -%]
3705 <!-- This will need refined/prettified. -->
3706 <div class="acq-claim-voucher">
3707     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
3708     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
3709     <ul>
3710         [% FOR event IN target %]
3711         <li>
3712             Event type: [% event.type.code %]
3713             [% IF event.type.library_initiated %](Library initiated)[% END %]
3714             <br />
3715             Event date: [% event.event_date %]<br />
3716             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
3717             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
3718             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
3719             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
3720             [% event.claim.lineitem_detail.fund.code %]
3721             ([% event.claim.lineitem_detail.fund.year %])
3722         </li>
3723         [% END %]
3724     </ul>
3725 </div>
3726 $$
3727 );
3728
3729 INSERT INTO action_trigger.environment (event_def, path) VALUES
3730     (21, 'claim'),
3731     (21, 'claim.type'),
3732     (21, 'claim.lineitem_detail'),
3733     (21, 'claim.lineitem_detail.fund'),
3734     (21, 'claim.lineitem_detail.lineitem.attributes'),
3735     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
3736     (21, 'creator'),
3737     (21, 'type')
3738 ;
3739
3740 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3741     VALUES (
3742         'format.acqinv.html',
3743         'acqinv',
3744         'Formats invoices into a voucher',
3745         TRUE
3746     );
3747
3748 INSERT INTO action_trigger.event_definition (
3749         id, active, owner, name, hook,
3750         validator, reactor, granularity, template
3751     ) VALUES (
3752         22,
3753         TRUE,
3754         1,
3755         'Invoice',
3756         'format.acqinv.html',
3757         'NOOP_True',
3758         'ProcessTemplate',
3759         'print-on-demand',
3760 $$
3761 [% FILTER collapse %]
3762 [%- SET invoice = target -%]
3763 <!-- This lacks totals, info about funds (for invoice entries,
3764     funds are per-LID!), and general refinement -->
3765 <div class="acq-invoice-voucher">
3766     <h1>Invoice</h1>
3767     <div>
3768         <strong>No.</strong> [% invoice.inv_ident %]
3769         [% IF invoice.inv_type %]
3770             / <strong>Type:</strong>[% invoice.inv_type %]
3771         [% END %]
3772     </div>
3773     <div>
3774         <dl>
3775             [% BLOCK ent_with_address %]
3776             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3777             <dd>
3778                 [% IF ent.addresses.0 %]
3779                     [% SET addr = ent.addresses.0 %]
3780                     [% addr.street1 %]<br />
3781                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3782                     [% addr.city %],
3783                     [% IF addr.county %] [% addr.county %], [% END %]
3784                     [% IF addr.state %] [% addr.state %] [% END %]
3785                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3786                     [% IF addr.country %] [% addr.country %] [% END %]
3787                 [% END %]
3788                 <p>
3789                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3790                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3791                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3792                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3793                 </p>
3794             </dd>
3795             [% END %]
3796             [% INCLUDE ent_with_address
3797                 ent = invoice.provider
3798                 ent_label = "Provider" %]
3799             [% INCLUDE ent_with_address
3800                 ent = invoice.shipper
3801                 ent_label = "Shipper" %]
3802             <dt>Receiver</dt>
3803             <dd>
3804                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3805             </dd>
3806             <dt>Received</dt>
3807             <dd>
3808                 [% helpers.format_date(invoice.recv_date) %] by
3809                 [% invoice.recv_method %]
3810             </dd>
3811             [% IF invoice.note %]
3812                 <dt>Note</dt>
3813                 <dd>
3814                     [% invoice.note %]
3815                 </dd>
3816             [% END %]
3817         </dl>
3818     </div>
3819     <ul>
3820         [% FOR entry IN invoice.entries %]
3821             <li>
3822                 [% IF entry.lineitem %]
3823                     Title: [% helpers.get_li_attr(
3824                         "title", "", entry.lineitem.attributes
3825                     ) %]<br />
3826                     Author: [% helpers.get_li_attr(
3827                         "author", "", entry.lineitem.attributes
3828                     ) %]
3829                 [% END %]
3830                 [% IF entry.purchase_order %]
3831                     (PO: [% entry.purchase_order.name %])
3832                 [% END %]<br />
3833                 Invoice item count: [% entry.inv_item_count %]
3834                 [% IF entry.phys_item_count %]
3835                     / Physical item count: [% entry.phys_item_count %]
3836                 [% END %]
3837                 <br />
3838                 [% IF entry.cost_billed %]
3839                     Cost billed: [% entry.cost_billed %]
3840                     [% IF entry.billed_per_item %](per item)[% END %]
3841                     <br />
3842                 [% END %]
3843                 [% IF entry.actual_cost %]
3844                     Actual cost: [% entry.actual_cost %]<br />
3845                 [% END %]
3846                 [% IF entry.amount_paid %]
3847                     Amount paid: [% entry.amount_paid %]<br />
3848                 [% END %]
3849                 [% IF entry.note %]Note: [% entry.note %][% END %]
3850             </li>
3851         [% END %]
3852         [% FOR item IN invoice.items %]
3853             <li>
3854                 [% IF item.inv_item_type %]
3855                     Item Type: [% item.inv_item_type %]<br />
3856                 [% END %]
3857                 [% IF item.title %]Title/Description:
3858                     [% item.title %]<br />
3859                 [% END %]
3860                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3861                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3862                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3863                 [% IF item.cost_billed %]
3864                     Cost billed: [% item.cost_billed %]<br />
3865                 [% END %]
3866                 [% IF item.actual_cost %]
3867                     Actual cost: [% item.actual_cost %]<br />
3868                 [% END %]
3869                 [% IF item.amount_paid %]
3870                     Amount paid: [% item.amount_paid %]<br />
3871                 [% END %]
3872             </li>
3873         [% END %]
3874     </ul>
3875 </div>
3876 [% END %]
3877 $$
3878 );
3879
3880 INSERT INTO action_trigger.environment (event_def, path) VALUES
3881     (22, 'provider'),
3882     (22, 'provider.addresses'),
3883     (22, 'shipper'),
3884     (22, 'shipper.addresses'),
3885     (22, 'receiver'),
3886     (22, 'entries'),
3887     (22, 'entries.purchase_order'),
3888     (22, 'entries.lineitem'),
3889     (22, 'entries.lineitem.attributes'),
3890     (22, 'items')
3891 ;
3892
3893 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3894   (23, 'provider.edi_default');
3895
3896 INSERT INTO action_trigger.validator (module, description) 
3897     VALUES (
3898         'Acq::PurchaseOrderEDIRequired',
3899         oils_i18n_gettext(
3900             'Acq::PurchaseOrderEDIRequired',
3901             'Purchase order is delivered via EDI',
3902             'atval',
3903             'description'
3904         )
3905     );
3906
3907 INSERT INTO action_trigger.reactor (module, description)
3908     VALUES (
3909         'GeneratePurchaseOrderJEDI',
3910         oils_i18n_gettext(
3911             'GeneratePurchaseOrderJEDI',
3912             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
3913             'atreact',
3914             'description'
3915         )
3916     );
3917
3918 UPDATE action_trigger.hook 
3919     SET 
3920         key = 'acqpo.activated', 
3921         passive = FALSE,
3922         description = oils_i18n_gettext(
3923             'acqpo.activated',
3924             'Purchase order was activated',
3925             'ath',
3926             'description'
3927         )
3928     WHERE key = 'format.po.jedi';
3929
3930 -- We just changed a key in action_trigger.hook.  Now redirect any
3931 -- child rows to point to the new key.  (There probably aren't any;
3932 -- this is just a precaution against possible local modifications.)
3933
3934 UPDATE action_trigger.event_definition
3935 SET hook = 'acqpo.activated'
3936 WHERE hook = 'format.po.jedi';
3937
3938 INSERT INTO action_trigger.reactor (module, description) VALUES (
3939     'AstCall', 'Possibly place a phone call with Asterisk'
3940 );
3941
3942 INSERT INTO
3943     action_trigger.event_definition (
3944         id, active, owner, name, hook, validator, reactor,
3945         cleanup_success, cleanup_failure, delay, delay_field, group_field,
3946         max_delay, granularity, usr_field, opt_in_setting, template
3947     ) VALUES (
3948         24,
3949         FALSE,
3950         1,
3951         'Telephone Overdue Notice',
3952         'checkout.due', 'NOOP_True', 'AstCall',
3953         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
3954         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
3955         $$
3956 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
3957 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
3958 Channel: [% channel_prefix %]/[% country %][% phone %]
3959 Context: overdue-test
3960 MaxRetries: 1
3961 RetryTime: 60
3962 WaitTime: 30
3963 Extension: 10
3964 Archive: 1
3965 Set: eg_user_id=[% target.0.usr.id %]
3966 Set: items=[% target.size %]
3967 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
3968 $$
3969     );
3970
3971 INSERT INTO
3972     action_trigger.environment (id, event_def, path)
3973     VALUES
3974         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
3975         (DEFAULT, 24, 'usr')
3976     ;
3977
3978 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3979         'circ.format.history.email',
3980         'circ', 
3981         oils_i18n_gettext(
3982             'circ.format.history.email',
3983             'An email has been requested for a circ history.',
3984             'ath',
3985             'description'
3986         ), 
3987         FALSE
3988     )
3989     ,(
3990         'circ.format.history.print',
3991         'circ', 
3992         oils_i18n_gettext(
3993             'circ.format.history.print',
3994             'A circ history needs to be formatted for printing.',
3995             'ath',
3996             'description'
3997         ), 
3998         FALSE
3999     )
4000     ,(
4001         'ahr.format.history.email',
4002         'ahr', 
4003         oils_i18n_gettext(
4004             'ahr.format.history.email',
4005             'An email has been requested for a hold request history.',
4006             'ath',
4007             'description'
4008         ), 
4009         FALSE
4010     )
4011     ,(
4012         'ahr.format.history.print',
4013         'ahr', 
4014         oils_i18n_gettext(
4015             'ahr.format.history.print',
4016             'A hold request history needs to be formatted for printing.',
4017             'ath',
4018             'description'
4019         ), 
4020         FALSE
4021     )
4022
4023 ;
4024
4025 INSERT INTO action_trigger.event_definition (
4026         id,
4027         active,
4028         owner,
4029         name,
4030         hook,
4031         validator,
4032         reactor,
4033         group_field,
4034         granularity,
4035         template
4036     ) VALUES (
4037         25,
4038         TRUE,
4039         1,
4040         'circ.history.email',
4041         'circ.format.history.email',
4042         'NOOP_True',
4043         'SendEmail',
4044         'usr',
4045         NULL,
4046 $$
4047 [%- USE date -%]
4048 [%- SET user = target.0.usr -%]
4049 To: [%- params.recipient_email || user.email %]
4050 From: [%- params.sender_email || default_sender %]
4051 Subject: Circulation History
4052
4053     [% FOR circ IN target %]
4054             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4055             Barcode: [% circ.target_copy.barcode %]
4056             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
4057             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
4058             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
4059     [% END %]
4060 $$
4061     )
4062     ,(
4063         26,
4064         TRUE,
4065         1,
4066         'circ.history.print',
4067         'circ.format.history.print',
4068         'NOOP_True',
4069         'ProcessTemplate',
4070         'usr',
4071         'print-on-demand',
4072 $$
4073 [%- USE date -%]
4074 <div>
4075     <style> li { padding: 8px; margin 5px; }</style>
4076     <div>[% date.format %]</div>
4077     <br/>
4078
4079     [% user.family_name %], [% user.first_given_name %]
4080     <ol>
4081     [% FOR circ IN target %]
4082         <li>
4083             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
4084             <div>Barcode: [% circ.target_copy.barcode %]</div>
4085             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
4086             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
4087             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
4088         </li>
4089     [% END %]
4090     </ol>
4091 </div>
4092 $$
4093     )
4094     ,(
4095         27,
4096         TRUE,
4097         1,
4098         'ahr.history.email',
4099         'ahr.format.history.email',
4100         'NOOP_True',
4101         'SendEmail',
4102         'usr',
4103         NULL,
4104 $$
4105 [%- USE date -%]
4106 [%- SET user = target.0.usr -%]
4107 To: [%- params.recipient_email || user.email %]
4108 From: [%- params.sender_email || default_sender %]
4109 Subject: Hold Request History
4110
4111     [% FOR hold IN target %]
4112             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
4113             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
4114             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
4115     [% END %]
4116 $$
4117     )
4118     ,(
4119         28,
4120         TRUE,
4121         1,
4122         'ahr.history.print',
4123         'ahr.format.history.print',
4124         'NOOP_True',
4125         'ProcessTemplate',
4126         'usr',
4127         'print-on-demand',
4128 $$
4129 [%- USE date -%]
4130 <div>
4131     <style> li { padding: 8px; margin 5px; }</style>
4132     <div>[% date.format %]</div>
4133     <br/>
4134
4135     [% user.family_name %], [% user.first_given_name %]
4136     <ol>
4137     [% FOR hold IN target %]
4138         <li>
4139             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
4140             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
4141             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
4142         </li>
4143     [% END %]
4144     </ol>
4145 </div>
4146 $$
4147     )
4148
4149 ;
4150
4151 INSERT INTO action_trigger.environment (
4152         event_def,
4153         path
4154     ) VALUES 
4155          ( 25, 'target_copy')
4156         ,( 25, 'usr' )
4157         ,( 26, 'target_copy' )
4158         ,( 26, 'usr' )
4159         ,( 27, 'current_copy' )
4160         ,( 27, 'usr' )
4161         ,( 28, 'current_copy' )
4162         ,( 28, 'usr' )
4163 ;
4164
4165 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4166         'money.format.payment_receipt.email',
4167         'mp', 
4168         oils_i18n_gettext(
4169             'money.format.payment_receipt.email',
4170             'An email has been requested for a payment receipt.',
4171             'ath',
4172             'description'
4173         ), 
4174         FALSE
4175     )
4176     ,(
4177         'money.format.payment_receipt.print',
4178         'mp', 
4179         oils_i18n_gettext(
4180             'money.format.payment_receipt.print',
4181             'A payment receipt needs to be formatted for printing.',
4182             'ath',
4183             'description'
4184         ), 
4185         FALSE
4186     )
4187 ;
4188
4189 INSERT INTO action_trigger.event_definition (
4190         id,
4191         active,
4192         owner,
4193         name,
4194         hook,
4195         validator,
4196         reactor,
4197         group_field,
4198         granularity,
4199         template
4200     ) VALUES (
4201         29,
4202         TRUE,
4203         1,
4204         'money.payment_receipt.email',
4205         'money.format.payment_receipt.email',
4206         'NOOP_True',
4207         'SendEmail',
4208         'xact.usr',
4209         NULL,
4210 $$
4211 [%- USE date -%]
4212 [%- SET user = target.0.xact.usr -%]
4213 To: [%- params.recipient_email || user.email %]
4214 From: [%- params.sender_email || default_sender %]
4215 Subject: Payment Receipt
4216
4217 [% date.format -%]
4218 [%- SET xact_mp_hash = {} -%]
4219 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
4220     [%- SET xact_id = mp.xact.id -%]
4221     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
4222     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
4223 [%- END -%]
4224 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
4225     [%- SET xact = xact_mp_hash.$xact_id.xact %]
4226 Transaction ID: [% xact_id %]
4227     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4228     [% ELSE %]Miscellaneous
4229     [% END %]
4230     Line item billings:
4231         [%- SET mb_type_hash = {} -%]
4232         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
4233             [%- IF mb.voided == 'f' -%]
4234                 [%- SET mb_type = mb.btype.id -%]
4235                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
4236                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
4237                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
4238                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
4239                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
4240             [%- END -%]
4241         [%- END -%]
4242         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
4243             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
4244                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4245                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4246             [%- ELSE -%][%# all other billings show individually %]
4247                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
4248                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4249                 [% END %]
4250             [% END %]
4251         [% END %]
4252     Line item payments:
4253         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4254             Payment ID: [% mp.id %]
4255                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4256                     [% CASE "cash_payment" %]cash
4257                     [% CASE "check_payment" %]check
4258                     [% CASE "credit_card_payment" %]credit card (
4259                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4260                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4261                         [% cc_chunks.last -%]
4262                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4263                     )
4264                     [% CASE "credit_payment" %]credit
4265                     [% CASE "forgive_payment" %]forgiveness
4266                     [% CASE "goods_payment" %]goods
4267                     [% CASE "work_payment" %]work
4268                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
4269         [% END %]
4270 [% END %]
4271 $$
4272     )
4273     ,(
4274         30,
4275         TRUE,
4276         1,
4277         'money.payment_receipt.print',
4278         'money.format.payment_receipt.print',
4279         'NOOP_True',
4280         'ProcessTemplate',
4281         'xact.usr',
4282         'print-on-demand',
4283 $$
4284 [%- USE date -%][%- SET user = target.0.xact.usr -%]
4285 <div style="li { padding: 8px; margin 5px; }">
4286     <div>[% date.format %]</div><br/>
4287     <ol>
4288     [% SET xact_mp_hash = {} %]
4289     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
4290         [% SET xact_id = mp.xact.id %]
4291         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
4292         [% xact_mp_hash.$xact_id.payments.push(mp) %]
4293     [% END %]
4294     [% FOR xact_id IN xact_mp_hash.keys.sort %]
4295         [% SET xact = xact_mp_hash.$xact_id.xact %]
4296         <li>Transaction ID: [% xact_id %]
4297             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4298             [% ELSE %]Miscellaneous
4299             [% END %]
4300             Line item billings:<ol>
4301                 [% SET mb_type_hash = {} %]
4302                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
4303                     [% IF mb.voided == 'f' %]
4304                         [% SET mb_type = mb.btype.id %]
4305                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
4306                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
4307                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
4308                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
4309                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
4310                     [% END %]
4311                 [% END %]
4312                 [% FOR mb_type IN mb_type_hash.keys.sort %]
4313                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
4314                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4315                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4316                     [% ELSE %][%# all other billings show individually %]
4317                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
4318                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4319                         [% END %]
4320                     [% END %]</li>
4321                 [% END %]
4322             </ol>
4323             Line item payments:<ol>
4324                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4325                     <li>Payment ID: [% mp.id %]
4326                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4327                             [% CASE "cash_payment" %]cash
4328                             [% CASE "check_payment" %]check
4329                             [% CASE "credit_card_payment" %]credit card (
4330                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4331                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4332                                 [% cc_chunks.last -%]
4333                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4334                             )
4335                             [% CASE "credit_payment" %]credit
4336                             [% CASE "forgive_payment" %]forgiveness
4337                             [% CASE "goods_payment" %]goods
4338                             [% CASE "work_payment" %]work
4339                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
4340                     </li>
4341                 [% END %]
4342             </ol>
4343         </li>
4344     [% END %]
4345     </ol>
4346 </div>
4347 $$
4348     )
4349 ;
4350
4351 INSERT INTO action_trigger.environment (
4352         event_def,
4353         path
4354     ) VALUES -- for fleshing mp objects
4355          ( 29, 'xact')
4356         ,( 29, 'xact.usr')
4357         ,( 29, 'xact.grocery' )
4358         ,( 29, 'xact.circulation' )
4359         ,( 29, 'xact.summary' )
4360         ,( 30, 'xact')
4361         ,( 30, 'xact.usr')
4362         ,( 30, 'xact.grocery' )
4363         ,( 30, 'xact.circulation' )
4364         ,( 30, 'xact.summary' )
4365 ;
4366
4367 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
4368     'DeleteTempBiblioBucket',
4369     oils_i18n_gettext(
4370         'DeleteTempBiblioBucket',
4371         'Deletes a cbreb object used as a target if it has a btype of "temp"',
4372         'atclean',
4373         'description'
4374     )
4375 );
4376
4377 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4378         'biblio.format.record_entry.email',
4379         'cbreb', 
4380         oils_i18n_gettext(
4381             'biblio.format.record_entry.email',
4382             'An email has been requested for one or more biblio record entries.',
4383             'ath',
4384             'description'
4385         ), 
4386         FALSE
4387     )
4388     ,(
4389         'biblio.format.record_entry.print',
4390         'cbreb', 
4391         oils_i18n_gettext(
4392             'biblio.format.record_entry.print',
4393             'One or more biblio record entries need to be formatted for printing.',
4394             'ath',
4395             'description'
4396         ), 
4397         FALSE
4398     )
4399 ;
4400
4401 INSERT INTO action_trigger.event_definition (
4402         id,
4403         active,
4404         owner,
4405         name,
4406         hook,
4407         validator,
4408         reactor,
4409         cleanup_success,
4410         cleanup_failure,
4411         group_field,
4412         granularity,
4413         template
4414     ) VALUES (
4415         31,
4416         TRUE,
4417         1,
4418         'biblio.record_entry.email',
4419         'biblio.format.record_entry.email',
4420         'NOOP_True',
4421         'SendEmail',
4422         'DeleteTempBiblioBucket',
4423         'DeleteTempBiblioBucket',
4424         'owner',
4425         NULL,
4426 $$
4427 [%- USE date -%]
4428 [%- SET user = target.0.owner -%]
4429 To: [%- params.recipient_email || user.email %]
4430 From: [%- params.sender_email || default_sender %]
4431 Subject: Bibliographic Records
4432
4433     [% FOR cbreb IN target %]
4434     [% FOR cbrebi IN cbreb.items %]
4435         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
4436         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
4437         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
4438         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4439
4440     [% END %]
4441     [% END %]
4442 $$
4443     )
4444     ,(
4445         32,
4446         TRUE,
4447         1,
4448         'biblio.record_entry.print',
4449         'biblio.format.record_entry.print',
4450         'NOOP_True',
4451         'ProcessTemplate',
4452         'DeleteTempBiblioBucket',
4453         'DeleteTempBiblioBucket',
4454         'owner',
4455         'print-on-demand',
4456 $$
4457 [%- USE date -%]
4458 <div>
4459     <style> li { padding: 8px; margin 5px; }</style>
4460     <ol>
4461     [% FOR cbreb IN target %]
4462     [% FOR cbrebi IN cbreb.items %]
4463         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
4464             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
4465             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
4466             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4467         </li>
4468     [% END %]
4469     [% END %]
4470     </ol>
4471 </div>
4472 $$
4473     )
4474 ;
4475
4476 INSERT INTO action_trigger.environment (
4477         event_def,
4478         path
4479     ) VALUES -- for fleshing cbreb objects
4480          ( 31, 'owner' )
4481         ,( 31, 'items' )
4482         ,( 31, 'items.target_biblio_record_entry' )
4483         ,( 31, 'items.target_biblio_record_entry.simple_record' )
4484         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
4485         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
4486         ,( 31, 'items.target_biblio_record_entry.notes' )
4487         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
4488         ,( 32, 'owner' )
4489         ,( 32, 'items' )
4490         ,( 32, 'items.target_biblio_record_entry' )
4491         ,( 32, 'items.target_biblio_record_entry.simple_record' )
4492         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
4493         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
4494         ,( 32, 'items.target_biblio_record_entry.notes' )
4495         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
4496 ;
4497
4498 INSERT INTO action_trigger.environment (
4499         event_def,
4500         path
4501     ) VALUES -- for fleshing mp objects
4502          ( 29, 'credit_card_payment')
4503         ,( 29, 'xact.billings')
4504         ,( 29, 'xact.billings.btype')
4505         ,( 30, 'credit_card_payment')
4506         ,( 30, 'xact.billings')
4507         ,( 30, 'xact.billings.btype')
4508 ;
4509
4510 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
4511     (   'circ.format.missing_pieces.slip.print',
4512         'circ', 
4513         oils_i18n_gettext(
4514             'circ.format.missing_pieces.slip.print',
4515             'A missing pieces slip needs to be formatted for printing.',
4516             'ath',
4517             'description'
4518         ), 
4519         FALSE
4520     )
4521     ,(  'circ.format.missing_pieces.letter.print',
4522         'circ', 
4523         oils_i18n_gettext(
4524             'circ.format.missing_pieces.letter.print',
4525             'A missing pieces patron letter needs to be formatted for printing.',
4526             'ath',
4527             'description'
4528         ), 
4529         FALSE
4530     )
4531 ;
4532
4533 INSERT INTO action_trigger.event_definition (
4534         id,
4535         active,
4536         owner,
4537         name,
4538         hook,
4539         validator,
4540         reactor,
4541         group_field,
4542         granularity,
4543         template
4544     ) VALUES (
4545         33,
4546         TRUE,
4547         1,
4548         'circ.missing_pieces.slip.print',
4549         'circ.format.missing_pieces.slip.print',
4550         'NOOP_True',
4551         'ProcessTemplate',
4552         'usr',
4553         'print-on-demand',
4554 $$
4555 [%- USE date -%]
4556 [%- SET user = target.0.usr -%]
4557 <div style="li { padding: 8px; margin 5px; }">
4558     <div>[% date.format %]</div><br/>
4559     Missing pieces for:
4560     <ol>
4561     [% FOR circ IN target %]
4562         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
4563             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4564         </li>
4565     [% END %]
4566     </ol>
4567 </div>
4568 $$
4569     )
4570     ,(
4571         34,
4572         TRUE,
4573         1,
4574         'circ.missing_pieces.letter.print',
4575         'circ.format.missing_pieces.letter.print',
4576         'NOOP_True',
4577         'ProcessTemplate',
4578         'usr',
4579         'print-on-demand',
4580 $$
4581 [%- USE date -%]
4582 [%- SET user = target.0.usr -%]
4583 [% date.format %]
4584 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
4585
4586 We are missing pieces for the following returned items:
4587 [% FOR circ IN target %]
4588 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
4589 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4590 [% END %]
4591
4592 Please return these pieces as soon as possible.
4593
4594 Thanks!
4595
4596 Library Staff
4597 $$
4598     )
4599 ;
4600
4601 INSERT INTO action_trigger.environment (
4602         event_def,
4603         path
4604     ) VALUES -- for fleshing circ objects
4605          ( 33, 'usr')
4606         ,( 33, 'target_copy')
4607         ,( 33, 'target_copy.circ_lib')
4608         ,( 33, 'target_copy.circ_lib.mailing_address')
4609         ,( 33, 'target_copy.circ_lib.billing_address')
4610         ,( 33, 'target_copy.call_number')
4611         ,( 33, 'target_copy.call_number.owning_lib')
4612         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
4613         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
4614         ,( 33, 'circ_lib')
4615         ,( 33, 'circ_lib.mailing_address')
4616         ,( 33, 'circ_lib.billing_address')
4617         ,( 34, 'usr')
4618         ,( 34, 'target_copy')
4619         ,( 34, 'target_copy.circ_lib')
4620         ,( 34, 'target_copy.circ_lib.mailing_address')
4621         ,( 34, 'target_copy.circ_lib.billing_address')
4622         ,( 34, 'target_copy.call_number')
4623         ,( 34, 'target_copy.call_number.owning_lib')
4624         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
4625         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
4626         ,( 34, 'circ_lib')
4627         ,( 34, 'circ_lib.mailing_address')
4628         ,( 34, 'circ_lib.billing_address')
4629 ;
4630
4631 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
4632     VALUES (   
4633         'ahr.format.pull_list',
4634         'ahr', 
4635         oils_i18n_gettext(
4636             'ahr.format.pull_list',
4637             'Format holds pull list for printing',
4638             'ath',
4639             'description'
4640         ), 
4641         FALSE
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         35,
4657         TRUE,
4658         1,
4659         'Holds Pull List',
4660         'ahr.format.pull_list',
4661         'NOOP_True',
4662         'ProcessTemplate',
4663         'pickup_lib',
4664         'print-on-demand',
4665 $$
4666 [%- USE date -%]
4667 <style>
4668     table { border-collapse: collapse; } 
4669     td { padding: 5px; border-bottom: 1px solid #888; } 
4670     th { font-weight: bold; }
4671 </style>
4672 [% 
4673     # Sort the holds into copy-location buckets
4674     # In the main print loop, sort each bucket by callnumber before printing
4675     SET holds_list = [];
4676     SET loc_data = [];
4677     SET current_location = target.0.current_copy.location.id;
4678     FOR hold IN target;
4679         IF current_location != hold.current_copy.location.id;
4680             SET current_location = hold.current_copy.location.id;
4681             holds_list.push(loc_data);
4682             SET loc_data = [];
4683         END;
4684         SET hold_data = {
4685             'hold' => hold,
4686             'callnumber' => hold.current_copy.call_number.label
4687         };
4688         loc_data.push(hold_data);
4689     END;
4690     holds_list.push(loc_data)
4691 %]
4692 <table>
4693     <thead>
4694         <tr>
4695             <th>Title</th>
4696             <th>Author</th>
4697             <th>Shelving Location</th>
4698             <th>Call Number</th>
4699             <th>Barcode</th>
4700             <th>Patron</th>
4701         </tr>
4702     </thead>
4703     <tbody>
4704     [% FOR loc_data IN holds_list  %]
4705         [% FOR hold_data IN loc_data.sort('callnumber') %]
4706             [% 
4707                 SET hold = hold_data.hold;
4708                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
4709             %]
4710             <tr>
4711                 <td>[% copy_data.title | truncate %]</td>
4712                 <td>[% copy_data.author | truncate %]</td>
4713                 <td>[% hold.current_copy.location.name %]</td>
4714                 <td>[% hold.current_copy.call_number.label %]</td>
4715                 <td>[% hold.current_copy.barcode %]</td>
4716                 <td>[% hold.usr.card.barcode %]</td>
4717             </tr>
4718         [% END %]
4719     [% END %]
4720     <tbody>
4721 </table>
4722 $$
4723 );
4724
4725 INSERT INTO action_trigger.environment (
4726         event_def,
4727         path
4728     ) VALUES
4729         (35, 'current_copy.location'),
4730         (35, 'current_copy.call_number'),
4731         (35, 'usr.card'),
4732         (35, 'pickup_lib')
4733 ;
4734
4735 INSERT INTO action_trigger.validator (module, description) VALUES ( 
4736     'HoldIsCancelled', 
4737     oils_i18n_gettext( 
4738         'HoldIsCancelled', 
4739         'Check whether a hold request is cancelled.', 
4740         'atval', 
4741         'description' 
4742     ) 
4743 );
4744
4745 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
4746     (   'circ.staff_age_to_lost',
4747         'circ', 
4748         oils_i18n_gettext(
4749             'circ.staff_age_to_lost',
4750             'An overdue circulation should be aged to a Lost status.',
4751             'ath',
4752             'description'
4753         ), 
4754         TRUE
4755     )
4756 ;
4757
4758 INSERT INTO action_trigger.event_definition (
4759         id,
4760         active,
4761         owner,
4762         name,
4763         hook,
4764         validator,
4765         reactor,
4766         delay_field
4767     ) VALUES (
4768         36,
4769         FALSE,
4770         1,
4771         'circ.staff_age_to_lost',
4772         'circ.staff_age_to_lost',
4773         'CircIsOverdue',
4774         'MarkItemLost',
4775         'due_date'
4776     )
4777 ;
4778
4779
4780 -- Create the query schema, and the tables and views therein
4781
4782 DROP SCHEMA IF EXISTS sql CASCADE;
4783 DROP SCHEMA IF EXISTS query CASCADE;
4784
4785 CREATE SCHEMA query;
4786
4787 CREATE TABLE query.datatype (
4788         id              SERIAL            PRIMARY KEY,
4789         datatype_name   TEXT              NOT NULL UNIQUE,
4790         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
4791         is_composite    BOOL              NOT NULL DEFAULT FALSE,
4792         CONSTRAINT qdt_comp_not_num CHECK
4793         ( is_numeric IS FALSE OR is_composite IS FALSE )
4794 );
4795
4796 -- Define the most common datatypes in query.datatype.  Note that none of
4797 -- these stock datatypes specifies a width or precision.
4798
4799 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
4800 -- room for more stock datatypes if we ever want to add them.
4801
4802 SELECT setval( 'query.datatype_id_seq', 1000 );
4803
4804 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4805   VALUES (1, 'SMALLINT', true);
4806  
4807 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4808   VALUES (2, 'INTEGER', true);
4809  
4810 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4811   VALUES (3, 'BIGINT', true);
4812  
4813 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4814   VALUES (4, 'DECIMAL', true);
4815  
4816 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4817   VALUES (5, 'NUMERIC', true);
4818  
4819 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4820   VALUES (6, 'REAL', true);
4821  
4822 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4823   VALUES (7, 'DOUBLE PRECISION', true);
4824  
4825 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4826   VALUES (8, 'SERIAL', true);
4827  
4828 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4829   VALUES (9, 'BIGSERIAL', true);
4830  
4831 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4832   VALUES (10, 'MONEY', false);
4833  
4834 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4835   VALUES (11, 'VARCHAR', false);
4836  
4837 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4838   VALUES (12, 'CHAR', false);
4839  
4840 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4841   VALUES (13, 'TEXT', false);
4842  
4843 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4844   VALUES (14, '"char"', false);
4845  
4846 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4847   VALUES (15, 'NAME', false);
4848  
4849 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4850   VALUES (16, 'BYTEA', false);
4851  
4852 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4853   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
4854  
4855 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4856   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
4857  
4858 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4859   VALUES (19, 'DATE', false);
4860  
4861 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4862   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
4863  
4864 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4865   VALUES (21, 'TIME WITH TIME ZONE', false);
4866  
4867 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4868   VALUES (22, 'INTERVAL', false);
4869  
4870 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4871   VALUES (23, 'BOOLEAN', false);
4872  
4873 CREATE TABLE query.subfield (
4874         id              SERIAL            PRIMARY KEY,
4875         composite_type  INT               NOT NULL
4876                                           REFERENCES query.datatype(id)
4877                                           ON DELETE CASCADE
4878                                           DEFERRABLE INITIALLY DEFERRED,
4879         seq_no          INT               NOT NULL
4880                                           CONSTRAINT qsf_pos_seq_no
4881                                           CHECK( seq_no > 0 ),
4882         subfield_type   INT               NOT NULL
4883                                           REFERENCES query.datatype(id)
4884                                           DEFERRABLE INITIALLY DEFERRED,
4885         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
4886 );
4887
4888 CREATE TABLE query.function_sig (
4889         id              SERIAL            PRIMARY KEY,
4890         function_name   TEXT              NOT NULL,
4891         return_type     INT               REFERENCES query.datatype(id)
4892                                           DEFERRABLE INITIALLY DEFERRED,
4893         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
4894         CONSTRAINT qfd_rtn_or_aggr CHECK
4895         ( return_type IS NULL OR is_aggregate = FALSE )
4896 );
4897
4898 CREATE INDEX query_function_sig_name_idx 
4899         ON query.function_sig (function_name);
4900
4901 CREATE TABLE query.function_param_def (
4902         id              SERIAL            PRIMARY KEY,
4903         function_id     INT               NOT NULL
4904                                           REFERENCES query.function_sig( id )
4905                                           ON DELETE CASCADE
4906                                           DEFERRABLE INITIALLY DEFERRED,
4907         seq_no          INT               NOT NULL
4908                                           CONSTRAINT qfpd_pos_seq_no CHECK
4909                                           ( seq_no > 0 ),
4910         datatype        INT               NOT NULL
4911                                           REFERENCES query.datatype( id )
4912                                           DEFERRABLE INITIALLY DEFERRED,
4913         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
4914 );
4915
4916 CREATE TABLE  query.stored_query (
4917         id            SERIAL         PRIMARY KEY,
4918         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
4919                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
4920         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
4921         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
4922         from_clause   INT            , --REFERENCES query.from_clause
4923                                      --DEFERRABLE INITIALLY DEFERRED,
4924         where_clause  INT            , --REFERENCES query.expression
4925                                      --DEFERRABLE INITIALLY DEFERRED,
4926         having_clause INT            , --REFERENCES query.expression
4927                                      --DEFERRABLE INITIALLY DEFERRED
4928         limit_count   INT            , --REFERENCES query.expression( id )
4929                                      --DEFERRABLE INITIALLY DEFERRED,
4930         offset_count  INT            --REFERENCES query.expression( id )
4931                                      --DEFERRABLE INITIALLY DEFERRED
4932 );
4933
4934 -- (Foreign keys to be defined later after other tables are created)
4935
4936 CREATE TABLE query.query_sequence (
4937         id              SERIAL            PRIMARY KEY,
4938         parent_query    INT               NOT NULL
4939                                           REFERENCES query.stored_query
4940                                                                           ON DELETE CASCADE
4941                                                                           DEFERRABLE INITIALLY DEFERRED,
4942         seq_no          INT               NOT NULL,
4943         child_query     INT               NOT NULL
4944                                           REFERENCES query.stored_query
4945                                                                           ON DELETE CASCADE
4946                                                                           DEFERRABLE INITIALLY DEFERRED,
4947         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
4948 );
4949
4950 CREATE TABLE query.bind_variable (
4951         name          TEXT             PRIMARY KEY,
4952         type          TEXT             NOT NULL
4953                                            CONSTRAINT bind_variable_type CHECK
4954                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
4955         description   TEXT             NOT NULL,
4956         default_value TEXT,            -- to be encoded in JSON
4957         label         TEXT             NOT NULL
4958 );
4959
4960 CREATE TABLE query.expression (
4961         id            SERIAL        PRIMARY KEY,
4962         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
4963                                     ( type IN (
4964                                     'xbet',    -- between
4965                                     'xbind',   -- bind variable
4966                                     'xbool',   -- boolean
4967                                     'xcase',   -- case
4968                                     'xcast',   -- cast
4969                                     'xcol',    -- column
4970                                     'xex',     -- exists
4971                                     'xfunc',   -- function
4972                                     'xin',     -- in
4973                                     'xisnull', -- is null
4974                                     'xnull',   -- null
4975                                     'xnum',    -- number
4976                                     'xop',     -- operator
4977                                     'xser',    -- series
4978                                     'xstr',    -- string
4979                                     'xsubq'    -- subquery
4980                                                                 ) ),
4981         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
4982         parent_expr   INT           REFERENCES query.expression
4983                                     ON DELETE CASCADE
4984                                     DEFERRABLE INITIALLY DEFERRED,
4985         seq_no        INT           NOT NULL DEFAULT 1,
4986         literal       TEXT,
4987         table_alias   TEXT,
4988         column_name   TEXT,
4989         left_operand  INT           REFERENCES query.expression
4990                                     DEFERRABLE INITIALLY DEFERRED,
4991         operator      TEXT,
4992         right_operand INT           REFERENCES query.expression
4993                                     DEFERRABLE INITIALLY DEFERRED,
4994         function_id   INT           REFERENCES query.function_sig
4995                                     DEFERRABLE INITIALLY DEFERRED,
4996         subquery      INT           REFERENCES query.stored_query
4997                                     DEFERRABLE INITIALLY DEFERRED,
4998         cast_type     INT           REFERENCES query.datatype
4999                                     DEFERRABLE INITIALLY DEFERRED,
5000         negate        BOOL          NOT NULL DEFAULT FALSE,
5001         bind_variable TEXT          REFERENCES query.bind_variable
5002                                         DEFERRABLE INITIALLY DEFERRED
5003 );
5004
5005 CREATE UNIQUE INDEX query_expr_parent_seq
5006         ON query.expression( parent_expr, seq_no )
5007         WHERE parent_expr IS NOT NULL;
5008
5009 -- Due to some circular references, the following foreign key definitions
5010 -- had to be deferred until query.expression existed:
5011
5012 ALTER TABLE query.stored_query
5013         ADD FOREIGN KEY ( where_clause )
5014         REFERENCES query.expression( id )
5015         DEFERRABLE INITIALLY DEFERRED;
5016
5017 ALTER TABLE query.stored_query
5018         ADD FOREIGN KEY ( having_clause )
5019         REFERENCES query.expression( id )
5020         DEFERRABLE INITIALLY DEFERRED;
5021
5022 ALTER TABLE query.stored_query
5023     ADD FOREIGN KEY ( limit_count )
5024     REFERENCES query.expression( id )
5025     DEFERRABLE INITIALLY DEFERRED;
5026
5027 ALTER TABLE query.stored_query
5028     ADD FOREIGN KEY ( offset_count )
5029     REFERENCES query.expression( id )
5030     DEFERRABLE INITIALLY DEFERRED;
5031
5032 CREATE TABLE query.case_branch (
5033         id            SERIAL        PRIMARY KEY,
5034         parent_expr   INT           NOT NULL REFERENCES query.expression
5035                                     ON DELETE CASCADE
5036                                     DEFERRABLE INITIALLY DEFERRED,
5037         seq_no        INT           NOT NULL,
5038         condition     INT           REFERENCES query.expression
5039                                     DEFERRABLE INITIALLY DEFERRED,
5040         result        INT           NOT NULL REFERENCES query.expression
5041                                     DEFERRABLE INITIALLY DEFERRED,
5042         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
5043 );
5044
5045 CREATE TABLE query.from_relation (
5046         id               SERIAL        PRIMARY KEY,
5047         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
5048                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
5049         table_name       TEXT,
5050         class_name       TEXT,
5051         subquery         INT           REFERENCES query.stored_query,
5052         function_call    INT           REFERENCES query.expression,
5053         table_alias      TEXT,
5054         parent_relation  INT           REFERENCES query.from_relation
5055                                        ON DELETE CASCADE
5056                                        DEFERRABLE INITIALLY DEFERRED,
5057         seq_no           INT           NOT NULL DEFAULT 1,
5058         join_type        TEXT          CONSTRAINT good_join_type CHECK (
5059                                            join_type IS NULL OR join_type IN
5060                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
5061                                        ),
5062         on_clause        INT           REFERENCES query.expression
5063                                        DEFERRABLE INITIALLY DEFERRED,
5064         CONSTRAINT join_or_core CHECK (
5065         ( parent_relation IS NULL AND join_type IS NULL
5066           AND on_clause IS NULL )
5067         OR
5068         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
5069           AND on_clause IS NOT NULL )
5070         )
5071 );
5072
5073 CREATE UNIQUE INDEX from_parent_seq
5074         ON query.from_relation( parent_relation, seq_no )
5075         WHERE parent_relation IS NOT NULL;
5076
5077 -- The following foreign key had to be deferred until
5078 -- query.from_relation existed
5079
5080 ALTER TABLE query.stored_query
5081         ADD FOREIGN KEY (from_clause)
5082         REFERENCES query.from_relation
5083         DEFERRABLE INITIALLY DEFERRED;
5084
5085 CREATE TABLE query.record_column (
5086         id            SERIAL            PRIMARY KEY,
5087         from_relation INT               NOT NULL REFERENCES query.from_relation
5088                                         ON DELETE CASCADE
5089                                         DEFERRABLE INITIALLY DEFERRED,
5090         seq_no        INT               NOT NULL,
5091         column_name   TEXT              NOT NULL,
5092         column_type   INT               NOT NULL REFERENCES query.datatype
5093                                         ON DELETE CASCADE
5094                                                                         DEFERRABLE INITIALLY DEFERRED,
5095         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
5096 );
5097
5098 CREATE TABLE query.select_item (
5099         id               SERIAL         PRIMARY KEY,
5100         stored_query     INT            NOT NULL REFERENCES query.stored_query
5101                                         ON DELETE CASCADE
5102                                         DEFERRABLE INITIALLY DEFERRED,
5103         seq_no           INT            NOT NULL,
5104         expression       INT            NOT NULL REFERENCES query.expression
5105                                         DEFERRABLE INITIALLY DEFERRED,
5106         column_alias     TEXT,
5107         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
5108         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
5109 );
5110
5111 CREATE TABLE query.order_by_item (
5112         id               SERIAL         PRIMARY KEY,
5113         stored_query     INT            NOT NULL REFERENCES query.stored_query
5114                                         ON DELETE CASCADE
5115                                         DEFERRABLE INITIALLY DEFERRED,
5116         seq_no           INT            NOT NULL,
5117         expression       INT            NOT NULL REFERENCES query.expression
5118                                         ON DELETE CASCADE
5119                                         DEFERRABLE INITIALLY DEFERRED,
5120         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
5121 );
5122
5123 ------------------------------------------------------------
5124 -- Create updatable views for different kinds of expressions
5125 ------------------------------------------------------------
5126
5127 -- Create updatable view for BETWEEN expressions
5128
5129 CREATE OR REPLACE VIEW query.expr_xbet AS
5130     SELECT
5131                 id,
5132                 parenthesize,
5133                 parent_expr,
5134                 seq_no,
5135                 left_operand,
5136                 negate
5137     FROM
5138         query.expression
5139     WHERE
5140         type = 'xbet';
5141
5142 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
5143     ON INSERT TO query.expr_xbet
5144     DO INSTEAD
5145     INSERT INTO query.expression (
5146                 id,
5147                 type,
5148                 parenthesize,
5149                 parent_expr,
5150                 seq_no,
5151                 left_operand,
5152                 negate
5153     ) VALUES (
5154         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5155         'xbet',
5156         COALESCE(NEW.parenthesize, FALSE),
5157         NEW.parent_expr,
5158         COALESCE(NEW.seq_no, 1),
5159                 NEW.left_operand,
5160                 COALESCE(NEW.negate, false)
5161     );
5162
5163 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
5164     ON UPDATE TO query.expr_xbet
5165     DO INSTEAD
5166     UPDATE query.expression SET
5167         id = NEW.id,
5168         parenthesize = NEW.parenthesize,
5169         parent_expr = NEW.parent_expr,
5170         seq_no = NEW.seq_no,
5171                 left_operand = NEW.left_operand,
5172                 negate = NEW.negate
5173     WHERE
5174         id = OLD.id;
5175
5176 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
5177     ON DELETE TO query.expr_xbet
5178     DO INSTEAD
5179     DELETE FROM query.expression WHERE id = OLD.id;
5180
5181 -- Create updatable view for bind variable expressions
5182
5183 CREATE OR REPLACE VIEW query.expr_xbind AS
5184     SELECT
5185                 id,
5186                 parenthesize,
5187                 parent_expr,
5188                 seq_no,
5189                 bind_variable
5190     FROM
5191         query.expression
5192     WHERE
5193         type = 'xbind';
5194
5195 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
5196     ON INSERT TO query.expr_xbind
5197     DO INSTEAD
5198     INSERT INTO query.expression (
5199                 id,
5200                 type,
5201                 parenthesize,
5202                 parent_expr,
5203                 seq_no,
5204                 bind_variable
5205     ) VALUES (
5206         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5207         'xbind',
5208         COALESCE(NEW.parenthesize, FALSE),
5209         NEW.parent_expr,
5210         COALESCE(NEW.seq_no, 1),
5211                 NEW.bind_variable
5212     );
5213
5214 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
5215     ON UPDATE TO query.expr_xbind
5216     DO INSTEAD
5217     UPDATE query.expression SET
5218         id = NEW.id,
5219         parenthesize = NEW.parenthesize,
5220         parent_expr = NEW.parent_expr,
5221         seq_no = NEW.seq_no,
5222                 bind_variable = NEW.bind_variable
5223     WHERE
5224         id = OLD.id;
5225
5226 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
5227     ON DELETE TO query.expr_xbind
5228     DO INSTEAD
5229     DELETE FROM query.expression WHERE id = OLD.id;
5230
5231 -- Create updatable view for boolean expressions
5232
5233 CREATE OR REPLACE VIEW query.expr_xbool AS
5234     SELECT
5235                 id,
5236                 parenthesize,
5237                 parent_expr,
5238                 seq_no,
5239                 literal,
5240                 negate
5241     FROM
5242         query.expression
5243     WHERE
5244         type = 'xbool';
5245
5246 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
5247     ON INSERT TO query.expr_xbool
5248     DO INSTEAD
5249     INSERT INTO query.expression (
5250                 id,
5251                 type,
5252                 parenthesize,
5253                 parent_expr,
5254                 seq_no,
5255                 literal,
5256                 negate
5257     ) VALUES (
5258         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5259         'xbool',
5260         COALESCE(NEW.parenthesize, FALSE),
5261         NEW.parent_expr,
5262         COALESCE(NEW.seq_no, 1),
5263         NEW.literal,
5264                 COALESCE(NEW.negate, false)
5265     );
5266
5267 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
5268     ON UPDATE TO query.expr_xbool
5269     DO INSTEAD
5270     UPDATE query.expression SET
5271         id = NEW.id,
5272         parenthesize = NEW.parenthesize,
5273         parent_expr = NEW.parent_expr,
5274         seq_no = NEW.seq_no,
5275         literal = NEW.literal,
5276                 negate = NEW.negate
5277     WHERE
5278         id = OLD.id;
5279
5280 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
5281     ON DELETE TO query.expr_xbool
5282     DO INSTEAD
5283     DELETE FROM query.expression WHERE id = OLD.id;
5284
5285 -- Create updatable view for CASE expressions
5286
5287 CREATE OR REPLACE VIEW query.expr_xcase AS
5288     SELECT
5289                 id,
5290                 parenthesize,
5291                 parent_expr,
5292                 seq_no,
5293                 left_operand,
5294                 negate
5295     FROM
5296         query.expression
5297     WHERE
5298         type = 'xcase';
5299
5300 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
5301     ON INSERT TO query.expr_xcase
5302     DO INSTEAD
5303     INSERT INTO query.expression (
5304                 id,
5305                 type,
5306                 parenthesize,
5307                 parent_expr,
5308                 seq_no,
5309                 left_operand,
5310                 negate
5311     ) VALUES (
5312         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5313         'xcase',
5314         COALESCE(NEW.parenthesize, FALSE),
5315         NEW.parent_expr,
5316         COALESCE(NEW.seq_no, 1),
5317                 NEW.left_operand,
5318                 COALESCE(NEW.negate, false)
5319     );
5320
5321 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
5322     ON UPDATE TO query.expr_xcase
5323     DO INSTEAD
5324     UPDATE query.expression SET
5325         id = NEW.id,
5326         parenthesize = NEW.parenthesize,
5327         parent_expr = NEW.parent_expr,
5328         seq_no = NEW.seq_no,
5329                 left_operand = NEW.left_operand,
5330                 negate = NEW.negate
5331     WHERE
5332         id = OLD.id;
5333
5334 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
5335     ON DELETE TO query.expr_xcase
5336     DO INSTEAD
5337     DELETE FROM query.expression WHERE id = OLD.id;
5338
5339 -- Create updatable view for cast expressions
5340
5341 CREATE OR REPLACE VIEW query.expr_xcast AS
5342     SELECT
5343                 id,
5344                 parenthesize,
5345                 parent_expr,
5346                 seq_no,
5347                 left_operand,
5348                 cast_type,
5349                 negate
5350     FROM
5351         query.expression
5352     WHERE
5353         type = 'xcast';
5354
5355 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
5356     ON INSERT TO query.expr_xcast
5357     DO INSTEAD
5358     INSERT INTO query.expression (
5359         id,
5360         type,
5361         parenthesize,
5362         parent_expr,
5363         seq_no,
5364         left_operand,
5365         cast_type,
5366         negate
5367     ) VALUES (
5368         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5369         'xcast',
5370         COALESCE(NEW.parenthesize, FALSE),
5371         NEW.parent_expr,
5372         COALESCE(NEW.seq_no, 1),
5373         NEW.left_operand,
5374         NEW.cast_type,
5375         COALESCE(NEW.negate, false)
5376     );
5377
5378 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
5379     ON UPDATE TO query.expr_xcast
5380     DO INSTEAD
5381     UPDATE query.expression SET
5382         id = NEW.id,
5383         parenthesize = NEW.parenthesize,
5384         parent_expr = NEW.parent_expr,
5385         seq_no = NEW.seq_no,
5386                 left_operand = NEW.left_operand,
5387                 cast_type = NEW.cast_type,
5388                 negate = NEW.negate
5389     WHERE
5390         id = OLD.id;
5391
5392 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
5393     ON DELETE TO query.expr_xcast
5394     DO INSTEAD
5395     DELETE FROM query.expression WHERE id = OLD.id;
5396
5397 -- Create updatable view for column expressions
5398
5399 CREATE OR REPLACE VIEW query.expr_xcol AS
5400     SELECT
5401                 id,
5402                 parenthesize,
5403                 parent_expr,
5404                 seq_no,
5405                 table_alias,
5406                 column_name,
5407                 negate
5408     FROM
5409         query.expression
5410     WHERE
5411         type = 'xcol';
5412
5413 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
5414     ON INSERT TO query.expr_xcol
5415     DO INSTEAD
5416     INSERT INTO query.expression (
5417                 id,
5418                 type,
5419                 parenthesize,
5420                 parent_expr,
5421                 seq_no,
5422                 table_alias,
5423                 column_name,
5424                 negate
5425     ) VALUES (
5426         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5427         'xcol',
5428         COALESCE(NEW.parenthesize, FALSE),
5429         NEW.parent_expr,
5430         COALESCE(NEW.seq_no, 1),
5431                 NEW.table_alias,
5432                 NEW.column_name,
5433                 COALESCE(NEW.negate, false)
5434     );
5435
5436 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
5437     ON UPDATE TO query.expr_xcol
5438     DO INSTEAD
5439     UPDATE query.expression SET
5440         id = NEW.id,
5441         parenthesize = NEW.parenthesize,
5442         parent_expr = NEW.parent_expr,
5443         seq_no = NEW.seq_no,
5444                 table_alias = NEW.table_alias,
5445                 column_name = NEW.column_name,
5446                 negate = NEW.negate
5447     WHERE
5448         id = OLD.id;
5449
5450 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
5451     ON DELETE TO query.expr_xcol
5452     DO INSTEAD
5453     DELETE FROM query.expression WHERE id = OLD.id;
5454
5455 -- Create updatable view for EXISTS expressions
5456
5457 CREATE OR REPLACE VIEW query.expr_xex AS
5458     SELECT
5459                 id,
5460                 parenthesize,
5461                 parent_expr,
5462                 seq_no,
5463                 subquery,
5464                 negate
5465     FROM
5466         query.expression
5467     WHERE
5468         type = 'xex';
5469
5470 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
5471     ON INSERT TO query.expr_xex
5472     DO INSTEAD
5473     INSERT INTO query.expression (
5474                 id,
5475                 type,
5476                 parenthesize,
5477                 parent_expr,
5478                 seq_no,
5479                 subquery,
5480                 negate
5481     ) VALUES (
5482         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5483         'xex',
5484         COALESCE(NEW.parenthesize, FALSE),
5485         NEW.parent_expr,
5486         COALESCE(NEW.seq_no, 1),
5487                 NEW.subquery,
5488                 COALESCE(NEW.negate, false)
5489     );
5490
5491 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
5492     ON UPDATE TO query.expr_xex
5493     DO INSTEAD
5494     UPDATE query.expression SET
5495         id = NEW.id,
5496         parenthesize = NEW.parenthesize,
5497         parent_expr = NEW.parent_expr,
5498         seq_no = NEW.seq_no,
5499                 subquery = NEW.subquery,
5500                 negate = NEW.negate
5501     WHERE
5502         id = OLD.id;
5503
5504 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
5505     ON DELETE TO query.expr_xex
5506     DO INSTEAD
5507     DELETE FROM query.expression WHERE id = OLD.id;
5508
5509 -- Create updatable view for function call expressions
5510
5511 CREATE OR REPLACE VIEW query.expr_xfunc AS
5512     SELECT
5513         id,
5514         parenthesize,
5515         parent_expr,
5516         seq_no,
5517         column_name,
5518         function_id,
5519         negate
5520     FROM
5521         query.expression
5522     WHERE
5523         type = 'xfunc';
5524
5525 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
5526     ON INSERT TO query.expr_xfunc
5527     DO INSTEAD
5528     INSERT INTO query.expression (
5529         id,
5530         type,
5531         parenthesize,
5532         parent_expr,
5533         seq_no,
5534         column_name,
5535         function_id,
5536         negate
5537     ) VALUES (
5538         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5539         'xfunc',
5540         COALESCE(NEW.parenthesize, FALSE),
5541         NEW.parent_expr,
5542         COALESCE(NEW.seq_no, 1),
5543         NEW.column_name,
5544         NEW.function_id,
5545         COALESCE(NEW.negate, false)
5546     );
5547
5548 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
5549     ON UPDATE TO query.expr_xfunc
5550     DO INSTEAD
5551     UPDATE query.expression SET
5552         id = NEW.id,
5553         parenthesize = NEW.parenthesize,
5554         parent_expr = NEW.parent_expr,
5555         seq_no = NEW.seq_no,
5556         column_name = NEW.column_name,
5557         function_id = NEW.function_id,
5558         negate = NEW.negate
5559     WHERE
5560         id = OLD.id;
5561
5562 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
5563     ON DELETE TO query.expr_xfunc
5564     DO INSTEAD
5565     DELETE FROM query.expression WHERE id = OLD.id;
5566
5567 -- Create updatable view for IN expressions
5568
5569 CREATE OR REPLACE VIEW query.expr_xin AS
5570     SELECT
5571                 id,
5572                 parenthesize,
5573                 parent_expr,
5574                 seq_no,
5575                 left_operand,
5576                 subquery,
5577                 negate
5578     FROM
5579         query.expression
5580     WHERE
5581         type = 'xin';
5582
5583 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
5584     ON INSERT TO query.expr_xin
5585     DO INSTEAD
5586     INSERT INTO query.expression (
5587                 id,
5588                 type,
5589                 parenthesize,
5590                 parent_expr,
5591                 seq_no,
5592                 left_operand,
5593                 subquery,
5594                 negate
5595     ) VALUES (
5596         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5597         'xin',
5598         COALESCE(NEW.parenthesize, FALSE),
5599         NEW.parent_expr,
5600         COALESCE(NEW.seq_no, 1),
5601                 NEW.left_operand,
5602                 NEW.subquery,
5603                 COALESCE(NEW.negate, false)
5604     );
5605
5606 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
5607     ON UPDATE TO query.expr_xin
5608     DO INSTEAD
5609     UPDATE query.expression SET
5610         id = NEW.id,
5611         parenthesize = NEW.parenthesize,
5612         parent_expr = NEW.parent_expr,
5613         seq_no = NEW.seq_no,
5614                 left_operand = NEW.left_operand,
5615                 subquery = NEW.subquery,
5616                 negate = NEW.negate
5617     WHERE
5618         id = OLD.id;
5619
5620 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
5621     ON DELETE TO query.expr_xin
5622     DO INSTEAD
5623     DELETE FROM query.expression WHERE id = OLD.id;
5624
5625 -- Create updatable view for IS NULL expressions
5626
5627 CREATE OR REPLACE VIEW query.expr_xisnull AS
5628     SELECT
5629                 id,
5630                 parenthesize,
5631                 parent_expr,
5632                 seq_no,
5633                 left_operand,
5634                 negate
5635     FROM
5636         query.expression
5637     WHERE
5638         type = 'xisnull';
5639
5640 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
5641     ON INSERT TO query.expr_xisnull
5642     DO INSTEAD
5643     INSERT INTO query.expression (
5644                 id,
5645                 type,
5646                 parenthesize,
5647                 parent_expr,
5648                 seq_no,
5649                 left_operand,
5650                 negate
5651     ) VALUES (
5652         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5653         'xisnull',
5654         COALESCE(NEW.parenthesize, FALSE),
5655         NEW.parent_expr,
5656         COALESCE(NEW.seq_no, 1),
5657                 NEW.left_operand,
5658                 COALESCE(NEW.negate, false)
5659     );
5660
5661 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
5662     ON UPDATE TO query.expr_xisnull
5663     DO INSTEAD
5664     UPDATE query.expression SET
5665         id = NEW.id,
5666         parenthesize = NEW.parenthesize,
5667         parent_expr = NEW.parent_expr,
5668         seq_no = NEW.seq_no,
5669                 left_operand = NEW.left_operand,
5670                 negate = NEW.negate
5671     WHERE
5672         id = OLD.id;
5673
5674 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
5675     ON DELETE TO query.expr_xisnull
5676     DO INSTEAD
5677     DELETE FROM query.expression WHERE id = OLD.id;
5678
5679 -- Create updatable view for NULL expressions
5680
5681 CREATE OR REPLACE VIEW query.expr_xnull AS
5682     SELECT
5683                 id,
5684                 parenthesize,
5685                 parent_expr,
5686                 seq_no,
5687                 negate
5688     FROM
5689         query.expression
5690     WHERE
5691         type = 'xnull';
5692
5693 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
5694     ON INSERT TO query.expr_xnull
5695     DO INSTEAD
5696     INSERT INTO query.expression (
5697                 id,
5698                 type,
5699                 parenthesize,
5700                 parent_expr,
5701                 seq_no,
5702                 negate
5703     ) VALUES (
5704         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5705         'xnull',
5706         COALESCE(NEW.parenthesize, FALSE),
5707         NEW.parent_expr,
5708         COALESCE(NEW.seq_no, 1),
5709                 COALESCE(NEW.negate, false)
5710     );
5711
5712 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
5713     ON UPDATE TO query.expr_xnull
5714     DO INSTEAD
5715     UPDATE query.expression SET
5716         id = NEW.id,
5717         parenthesize = NEW.parenthesize,
5718         parent_expr = NEW.parent_expr,
5719         seq_no = NEW.seq_no,
5720                 negate = NEW.negate
5721     WHERE
5722         id = OLD.id;
5723
5724 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
5725     ON DELETE TO query.expr_xnull
5726     DO INSTEAD
5727     DELETE FROM query.expression WHERE id = OLD.id;
5728
5729 -- Create updatable view for numeric literal expressions
5730
5731 CREATE OR REPLACE VIEW query.expr_xnum AS
5732     SELECT
5733                 id,
5734                 parenthesize,
5735                 parent_expr,
5736                 seq_no,
5737                 literal
5738     FROM
5739         query.expression
5740     WHERE
5741         type = 'xnum';
5742
5743 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
5744     ON INSERT TO query.expr_xnum
5745     DO INSTEAD
5746     INSERT INTO query.expression (
5747                 id,
5748                 type,
5749                 parenthesize,
5750                 parent_expr,
5751                 seq_no,
5752                 literal
5753     ) VALUES (
5754         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5755         'xnum',
5756         COALESCE(NEW.parenthesize, FALSE),
5757         NEW.parent_expr,
5758         COALESCE(NEW.seq_no, 1),
5759         NEW.literal
5760     );
5761
5762 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
5763     ON UPDATE TO query.expr_xnum
5764     DO INSTEAD
5765     UPDATE query.expression SET
5766         id = NEW.id,
5767         parenthesize = NEW.parenthesize,
5768         parent_expr = NEW.parent_expr,
5769         seq_no = NEW.seq_no,
5770         literal = NEW.literal
5771     WHERE
5772         id = OLD.id;
5773
5774 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
5775     ON DELETE TO query.expr_xnum
5776     DO INSTEAD
5777     DELETE FROM query.expression WHERE id = OLD.id;
5778
5779 -- Create updatable view for operator expressions
5780
5781 CREATE OR REPLACE VIEW query.expr_xop AS
5782     SELECT
5783                 id,
5784                 parenthesize,
5785                 parent_expr,
5786                 seq_no,
5787                 left_operand,
5788                 operator,
5789                 right_operand,
5790                 negate
5791     FROM
5792         query.expression
5793     WHERE
5794         type = 'xop';
5795
5796 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
5797     ON INSERT TO query.expr_xop
5798     DO INSTEAD
5799     INSERT INTO query.expression (
5800                 id,
5801                 type,
5802                 parenthesize,
5803                 parent_expr,
5804                 seq_no,
5805                 left_operand,
5806                 operator,
5807                 right_operand,
5808                 negate
5809     ) VALUES (
5810         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5811         'xop',
5812         COALESCE(NEW.parenthesize, FALSE),
5813         NEW.parent_expr,
5814         COALESCE(NEW.seq_no, 1),
5815                 NEW.left_operand,
5816                 NEW.operator,
5817                 NEW.right_operand,
5818                 COALESCE(NEW.negate, false)
5819     );
5820
5821 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
5822     ON UPDATE TO query.expr_xop
5823     DO INSTEAD
5824     UPDATE query.expression SET
5825         id = NEW.id,
5826         parenthesize = NEW.parenthesize,
5827         parent_expr = NEW.parent_expr,
5828         seq_no = NEW.seq_no,
5829                 left_operand = NEW.left_operand,
5830                 operator = NEW.operator,
5831                 right_operand = NEW.right_operand,
5832                 negate = NEW.negate
5833     WHERE
5834         id = OLD.id;
5835
5836 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
5837     ON DELETE TO query.expr_xop
5838     DO INSTEAD
5839     DELETE FROM query.expression WHERE id = OLD.id;
5840
5841 -- Create updatable view for series expressions
5842 -- i.e. series of expressions separated by operators
5843
5844 CREATE OR REPLACE VIEW query.expr_xser AS
5845     SELECT
5846                 id,
5847                 parenthesize,
5848                 parent_expr,
5849                 seq_no,
5850                 operator,
5851                 negate
5852     FROM
5853         query.expression
5854     WHERE
5855         type = 'xser';
5856
5857 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
5858     ON INSERT TO query.expr_xser
5859     DO INSTEAD
5860     INSERT INTO query.expression (
5861                 id,
5862                 type,
5863                 parenthesize,
5864                 parent_expr,
5865                 seq_no,
5866                 operator,
5867                 negate
5868     ) VALUES (
5869         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5870         'xser',
5871         COALESCE(NEW.parenthesize, FALSE),
5872         NEW.parent_expr,
5873         COALESCE(NEW.seq_no, 1),
5874                 NEW.operator,
5875                 COALESCE(NEW.negate, false)
5876     );
5877
5878 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
5879     ON UPDATE TO query.expr_xser
5880     DO INSTEAD
5881     UPDATE query.expression SET
5882         id = NEW.id,
5883         parenthesize = NEW.parenthesize,
5884         parent_expr = NEW.parent_expr,
5885         seq_no = NEW.seq_no,
5886                 operator = NEW.operator,
5887                 negate = NEW.negate
5888     WHERE
5889         id = OLD.id;
5890
5891 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
5892     ON DELETE TO query.expr_xser
5893     DO INSTEAD
5894     DELETE FROM query.expression WHERE id = OLD.id;
5895
5896 -- Create updatable view for string literal expressions
5897
5898 CREATE OR REPLACE VIEW query.expr_xstr AS
5899     SELECT
5900         id,
5901         parenthesize,
5902         parent_expr,
5903         seq_no,
5904         literal
5905     FROM
5906         query.expression
5907     WHERE
5908         type = 'xstr';
5909
5910 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
5911     ON INSERT TO query.expr_xstr
5912     DO INSTEAD
5913     INSERT INTO query.expression (
5914         id,
5915         type,
5916         parenthesize,
5917         parent_expr,
5918         seq_no,
5919         literal
5920     ) VALUES (
5921         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5922         'xstr',
5923         COALESCE(NEW.parenthesize, FALSE),
5924         NEW.parent_expr,
5925         COALESCE(NEW.seq_no, 1),
5926         NEW.literal
5927     );
5928
5929 CREATE OR REPLACE RULE query_expr_string_update_rule AS
5930     ON UPDATE TO query.expr_xstr
5931     DO INSTEAD
5932     UPDATE query.expression SET
5933         id = NEW.id,
5934         parenthesize = NEW.parenthesize,
5935         parent_expr = NEW.parent_expr,
5936         seq_no = NEW.seq_no,
5937         literal = NEW.literal
5938     WHERE
5939         id = OLD.id;
5940
5941 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
5942     ON DELETE TO query.expr_xstr
5943     DO INSTEAD
5944     DELETE FROM query.expression WHERE id = OLD.id;
5945
5946 -- Create updatable view for subquery expressions
5947
5948 CREATE OR REPLACE VIEW query.expr_xsubq AS
5949     SELECT
5950                 id,
5951                 parenthesize,
5952                 parent_expr,
5953                 seq_no,
5954                 subquery,
5955                 negate
5956     FROM
5957         query.expression
5958     WHERE
5959         type = 'xsubq';
5960
5961 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
5962     ON INSERT TO query.expr_xsubq
5963     DO INSTEAD
5964     INSERT INTO query.expression (
5965                 id,
5966                 type,
5967                 parenthesize,
5968                 parent_expr,
5969                 seq_no,
5970                 subquery,
5971                 negate
5972     ) VALUES (
5973         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5974         'xsubq',
5975         COALESCE(NEW.parenthesize, FALSE),
5976         NEW.parent_expr,
5977         COALESCE(NEW.seq_no, 1),
5978                 NEW.subquery,
5979                 COALESCE(NEW.negate, false)
5980     );
5981
5982 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
5983     ON UPDATE TO query.expr_xsubq
5984     DO INSTEAD
5985     UPDATE query.expression SET
5986         id = NEW.id,
5987         parenthesize = NEW.parenthesize,
5988         parent_expr = NEW.parent_expr,
5989         seq_no = NEW.seq_no,
5990                 subquery = NEW.subquery,
5991                 negate = NEW.negate
5992     WHERE
5993         id = OLD.id;
5994
5995 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
5996     ON DELETE TO query.expr_xsubq
5997     DO INSTEAD
5998     DELETE FROM query.expression WHERE id = OLD.id;
5999
6000 CREATE TABLE action.fieldset (
6001     id              SERIAL          PRIMARY KEY,
6002     owner           INT             NOT NULL REFERENCES actor.usr (id)
6003                                     DEFERRABLE INITIALLY DEFERRED,
6004     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
6005                                     DEFERRABLE INITIALLY DEFERRED,
6006     status          TEXT            NOT NULL
6007                                     CONSTRAINT valid_status CHECK ( status in
6008                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
6009     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
6010     scheduled_time  TIMESTAMPTZ,
6011     applied_time    TIMESTAMPTZ,
6012     classname       TEXT            NOT NULL, -- an IDL class name
6013     name            TEXT            NOT NULL,
6014     stored_query    INT             REFERENCES query.stored_query (id)
6015                                     DEFERRABLE INITIALLY DEFERRED,
6016     pkey_value      TEXT,
6017     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
6018     CONSTRAINT fieldset_one_or_the_other CHECK (
6019         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
6020         (pkey_value IS NOT NULL AND stored_query IS NULL)
6021     )
6022     -- the CHECK constraint means we can update the fields for a single
6023     -- row without all the extra overhead involved in a query
6024 );
6025
6026 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
6027 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
6028
6029 CREATE TABLE action.fieldset_col_val (
6030     id              SERIAL  PRIMARY KEY,
6031     fieldset        INT     NOT NULL REFERENCES action.fieldset
6032                                          ON DELETE CASCADE
6033                                          DEFERRABLE INITIALLY DEFERRED,
6034     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
6035     val             TEXT,              -- value for the column ... NULL means, well, NULL
6036     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
6037 );
6038
6039 CREATE OR REPLACE FUNCTION action.apply_fieldset(
6040         fieldset_id IN INT,        -- id from action.fieldset
6041         table_name  IN TEXT,       -- table to be updated
6042         pkey_name   IN TEXT,       -- name of primary key column in that table
6043         query       IN TEXT        -- query constructed by qstore (for query-based
6044                                    --    fieldsets only; otherwise null
6045 )
6046 RETURNS TEXT AS $$
6047 DECLARE
6048         statement TEXT;
6049         fs_status TEXT;
6050         fs_pkey_value TEXT;
6051         fs_query TEXT;
6052         sep CHAR;
6053         status_code TEXT;
6054         msg TEXT;
6055         update_count INT;
6056         cv RECORD;
6057 BEGIN
6058         -- Sanity checks
6059         IF fieldset_id IS NULL THEN
6060                 RETURN 'Fieldset ID parameter is NULL';
6061         END IF;
6062         IF table_name IS NULL THEN
6063                 RETURN 'Table name parameter is NULL';
6064         END IF;
6065         IF pkey_name IS NULL THEN
6066                 RETURN 'Primary key name parameter is NULL';
6067         END IF;
6068         --
6069         statement := 'UPDATE ' || table_name || ' SET';
6070         --
6071         SELECT
6072                 status,
6073                 quote_literal( pkey_value )
6074         INTO
6075                 fs_status,
6076                 fs_pkey_value
6077         FROM
6078                 action.fieldset
6079         WHERE
6080                 id = fieldset_id;
6081         --
6082         IF fs_status IS NULL THEN
6083                 RETURN 'No fieldset found for id = ' || fieldset_id;
6084         ELSIF fs_status = 'APPLIED' THEN
6085                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
6086         END IF;
6087         --
6088         sep := '';
6089         FOR cv IN
6090                 SELECT  col,
6091                                 val
6092                 FROM    action.fieldset_col_val
6093                 WHERE   fieldset = fieldset_id
6094         LOOP
6095                 statement := statement || sep || ' ' || cv.col
6096                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
6097                 sep := ',';
6098         END LOOP;
6099         --
6100         IF sep = '' THEN
6101                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
6102         END IF;
6103         --
6104         -- Add the WHERE clause.  This differs according to whether it's a
6105         -- single-row fieldset or a query-based fieldset.
6106         --
6107         IF query IS NULL        AND fs_pkey_value IS NULL THEN
6108                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
6109         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
6110             fs_query := rtrim( query, ';' );
6111             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
6112                          || fs_query || ' );';
6113         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
6114                 statement := statement || ' WHERE ' || pkey_name || ' = '
6115                                      || fs_pkey_value || ';';
6116         ELSE  -- both are not null
6117                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
6118         END IF;
6119         --
6120         -- Execute the update
6121         --
6122         BEGIN
6123                 EXECUTE statement;
6124                 GET DIAGNOSTICS update_count = ROW_COUNT;
6125                 --
6126                 IF UPDATE_COUNT > 0 THEN
6127                         status_code := 'APPLIED';
6128                         msg := NULL;
6129                 ELSE
6130                         status_code := 'ERROR';
6131                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
6132         END IF;
6133         EXCEPTION WHEN OTHERS THEN
6134                 status_code := 'ERROR';
6135                 msg := 'Unable to apply fieldset ' || fieldset_id
6136                            || ': ' || sqlerrm;
6137         END;
6138         --
6139         -- Update fieldset status
6140         --
6141         UPDATE action.fieldset
6142         SET status       = status_code,
6143             applied_time = now()
6144         WHERE id = fieldset_id;
6145         --
6146         RETURN msg;
6147 END;
6148 $$ LANGUAGE plpgsql;
6149
6150 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
6151 /**
6152  * Applies a specified fieldset, using a supplied table name and primary
6153  * key name.  The query parameter should be non-null only for
6154  * query-based fieldsets.
6155  *
6156  * Returns NULL if successful, or an error message if not.
6157  */
6158 $$;
6159
6160 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
6161
6162 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
6163     SELECT  u.hold,
6164             c.circ_lib,
6165             count(*)
6166       FROM  action.unfulfilled_hold_list u
6167             JOIN asset.copy c ON (c.id = u.current_copy)
6168       GROUP BY 1,2;
6169
6170 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
6171     SELECT  hold,
6172             min(count)
6173       FROM  action.unfulfilled_hold_loops
6174       GROUP BY 1;
6175
6176 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
6177     SELECT  DISTINCT l.*
6178       FROM  action.unfulfilled_hold_loops l
6179             JOIN action.unfulfilled_hold_min_loop m USING (hold)
6180       WHERE l.count = m.min;
6181
6182 ALTER TABLE asset.copy
6183 ADD COLUMN dummy_isbn TEXT;
6184
6185 ALTER TABLE auditor.asset_copy_history
6186 ADD COLUMN dummy_isbn TEXT;
6187
6188 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
6189 -- Add corresponding new column to auditor.asset_copy_history
6190
6191 ALTER TABLE asset.copy
6192         ADD COLUMN status_changed_time TIMESTAMPTZ;
6193
6194 ALTER TABLE auditor.asset_copy_history
6195         ADD COLUMN status_changed_time TIMESTAMPTZ;
6196
6197 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
6198 RETURNS TRIGGER AS $$
6199 BEGIN
6200     IF NEW.status <> OLD.status THEN
6201         NEW.status_changed_time := now();
6202     END IF;
6203     RETURN NEW;
6204 END;
6205 $$ LANGUAGE plpgsql;
6206
6207 CREATE TRIGGER acp_status_changed_trig
6208         BEFORE UPDATE ON asset.copy
6209         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
6210
6211 ALTER TABLE asset.copy
6212 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6213
6214 ALTER TABLE auditor.asset_copy_history
6215 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6216
6217 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
6218 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
6219
6220 DROP INDEX IF EXISTS asset.copy_barcode_key;
6221 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
6222
6223 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
6224 -- AFTER INSERT OR UPDATE ON asset.copy
6225
6226 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
6227 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
6228
6229 -- Moke mostly parallel changes to action.circulation
6230 -- and action.aged_circulation
6231
6232 ALTER TABLE action.circulation
6233 ADD COLUMN workstation INT
6234     REFERENCES actor.workstation
6235         ON DELETE SET NULL
6236         DEFERRABLE INITIALLY DEFERRED;
6237
6238 ALTER TABLE action.aged_circulation
6239 ADD COLUMN workstation INT;
6240
6241 ALTER TABLE action.circulation
6242 ADD COLUMN parent_circ BIGINT
6243         REFERENCES action.circulation(id)
6244         DEFERRABLE INITIALLY DEFERRED;
6245
6246 CREATE UNIQUE INDEX circ_parent_idx
6247 ON action.circulation( parent_circ )
6248 WHERE parent_circ IS NOT NULL;
6249
6250 ALTER TABLE action.aged_circulation
6251 ADD COLUMN parent_circ BIGINT;
6252
6253 ALTER TABLE action.circulation
6254 ADD COLUMN checkin_workstation INT
6255         REFERENCES actor.workstation(id)
6256         ON DELETE SET NULL
6257         DEFERRABLE INITIALLY DEFERRED;
6258
6259 ALTER TABLE action.aged_circulation
6260 ADD COLUMN checkin_workstation INT;
6261
6262 ALTER TABLE action.circulation
6263 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6264
6265 ALTER TABLE action.aged_circulation
6266 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6267
6268 CREATE INDEX action_circulation_target_copy_idx
6269 ON action.circulation (target_copy);
6270
6271 CREATE INDEX action_aged_circulation_target_copy_idx
6272 ON action.aged_circulation (target_copy);
6273
6274 ALTER TABLE action.circulation
6275 DROP CONSTRAINT circulation_stop_fines_check;
6276
6277 ALTER TABLE action.circulation
6278         ADD CONSTRAINT circulation_stop_fines_check
6279         CHECK (stop_fines IN (
6280         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
6281
6282 -- Hard due-date functionality
6283 CREATE TABLE config.hard_due_date (
6284         id          SERIAL      PRIMARY KEY,
6285         name        TEXT        NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
6286         ceiling_date    TIMESTAMPTZ NOT NULL,
6287         forceto     BOOL        NOT NULL,
6288         owner       INT         NOT NULL
6289 );
6290
6291 CREATE TABLE config.hard_due_date_values (
6292     id                  SERIAL      PRIMARY KEY,
6293     hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
6294                                     DEFERRABLE INITIALLY DEFERRED,
6295     ceiling_date        TIMESTAMPTZ NOT NULL,
6296     active_date         TIMESTAMPTZ NOT NULL
6297 );
6298
6299 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN hard_due_date INT REFERENCES config.hard_due_date (id);
6300
6301 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
6302 DECLARE
6303     temp_value  config.hard_due_date_values%ROWTYPE;
6304     updated     INT := 0;
6305 BEGIN
6306     FOR temp_value IN
6307       SELECT  DISTINCT ON (hard_due_date) *
6308         FROM  config.hard_due_date_values
6309         WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
6310         ORDER BY active_date DESC -- Latest (nearest to us) active time
6311    LOOP
6312         UPDATE  config.hard_due_date
6313           SET   ceiling_date = temp_value.ceiling_date
6314           WHERE id = temp_value.hard_due_date
6315                 AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd
6316
6317         IF FOUND THEN
6318             updated := updated + 1;
6319         END IF;
6320     END LOOP;
6321
6322     RETURN updated;
6323 END;
6324 $func$ LANGUAGE plpgsql;
6325
6326 -- Correct some long-standing misspellings involving variations of "recur"
6327
6328 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
6329 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6330
6331 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
6332 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6333
6334 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
6335 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
6336
6337 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
6338
6339 -- Might as well keep the comment in sync as well
6340 COMMENT ON TABLE config.rule_recurring_fine IS $$
6341 /*
6342  * Copyright (C) 2005  Georgia Public Library Service 
6343  * Mike Rylander <mrylander@gmail.com>
6344  *
6345  * Circulation Recurring Fine rules
6346  *
6347  * Each circulation is given a recurring fine amount based on one of
6348  * these rules.  The recurrence_interval should not be any shorter
6349  * than the interval between runs of the fine_processor.pl script
6350  * (which is run from CRON), or you could miss fines.
6351  * 
6352  *
6353  * ****
6354  *
6355  * This program is free software; you can redistribute it and/or
6356  * modify it under the terms of the GNU General Public License
6357  * as published by the Free Software Foundation; either version 2
6358  * of the License, or (at your option) any later version.
6359  *
6360  * This program is distributed in the hope that it will be useful,
6361  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6362  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6363  * GNU General Public License for more details.
6364  */
6365 $$;
6366
6367 -- Extend the name change to some related views:
6368
6369 DROP VIEW IF EXISTS reporter.overdue_circs;
6370
6371 CREATE OR REPLACE VIEW reporter.overdue_circs AS
6372 SELECT  *
6373   FROM  action.circulation
6374     WHERE checkin_time is null
6375                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
6376                                 AND due_date < now();
6377
6378 DROP VIEW IF EXISTS stats.fleshed_circulation;
6379
6380 DROP VIEW IF EXISTS stats.fleshed_copy;
6381
6382 CREATE VIEW stats.fleshed_copy AS
6383         SELECT  cp.*,
6384         CAST(cp.create_date AS DATE) AS create_date_day,
6385         CAST(cp.edit_date AS DATE) AS edit_date_day,
6386         DATE_TRUNC('hour', cp.create_date) AS create_date_hour,
6387         DATE_TRUNC('hour', cp.edit_date) AS edit_date_hour,
6388                 cn.label AS call_number_label,
6389                 cn.owning_lib,
6390                 rd.item_lang,
6391                 rd.item_type,
6392                 rd.item_form
6393         FROM    asset.copy cp
6394                 JOIN asset.call_number cn ON (cp.call_number = cn.id)
6395                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
6396
6397 CREATE VIEW stats.fleshed_circulation AS
6398         SELECT  c.*,
6399                 CAST(c.xact_start AS DATE) AS start_date_day,
6400                 CAST(c.xact_finish AS DATE) AS finish_date_day,
6401                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
6402                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
6403                 cp.call_number_label,
6404                 cp.owning_lib,
6405                 cp.item_lang,
6406                 cp.item_type,
6407                 cp.item_form
6408         FROM    action.circulation c
6409                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
6410
6411 -- Drop a view temporarily in order to alter action.all_circulation, upon
6412 -- which it is dependent.  We will recreate the view later.
6413
6414 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
6415
6416 -- You would think that CREATE OR REPLACE would be enough, but in testing
6417 -- PostgreSQL complained about renaming the columns in the view. So we
6418 -- drop the view first.
6419 DROP VIEW IF EXISTS action.all_circulation;
6420
6421 CREATE OR REPLACE VIEW action.all_circulation AS
6422     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6423         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6424         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6425         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6426         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6427         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6428       FROM  action.aged_circulation
6429             UNION ALL
6430     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,
6431         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,
6432         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6433         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6434         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6435         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6436         circ.parent_circ
6437       FROM  action.circulation circ
6438         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6439         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6440         JOIN actor.usr p ON (circ.usr = p.id)
6441         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6442         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
6443
6444 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
6445
6446 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
6447  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
6448    FROM asset."copy" cp
6449    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
6450    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
6451    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
6452   GROUP BY cp.id;
6453
6454 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
6455
6456 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
6457
6458 -- Rebuild dependent views
6459
6460 DROP VIEW IF EXISTS action.billable_circulations;
6461
6462 CREATE OR REPLACE VIEW action.billable_circulations AS
6463     SELECT  *
6464       FROM  action.circulation
6465       WHERE xact_finish IS NULL;
6466
6467 DROP VIEW IF EXISTS action.open_circulation;
6468
6469 CREATE OR REPLACE VIEW action.open_circulation AS
6470     SELECT  *
6471       FROM  action.circulation
6472       WHERE checkin_time IS NULL
6473       ORDER BY due_date;
6474
6475 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
6476 DECLARE
6477 found char := 'N';
6478 BEGIN
6479
6480     -- If there are any renewals for this circulation, don't archive or delete
6481     -- it yet.   We'll do so later, when we archive and delete the renewals.
6482
6483     SELECT 'Y' INTO found
6484     FROM action.circulation
6485     WHERE parent_circ = OLD.id
6486     LIMIT 1;
6487
6488     IF found = 'Y' THEN
6489         RETURN NULL;  -- don't delete
6490         END IF;
6491
6492     -- Archive a copy of the old row to action.aged_circulation
6493
6494     INSERT INTO action.aged_circulation
6495         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6496         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6497         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6498         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6499         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6500         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
6501       SELECT
6502         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6503         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6504         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6505         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6506         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6507         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6508         FROM action.all_circulation WHERE id = OLD.id;
6509
6510     RETURN OLD;
6511 END;
6512 $$ LANGUAGE 'plpgsql';
6513
6514 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
6515
6516 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
6517
6518 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
6519
6520 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$
6521 DECLARE
6522     current_requestor_group    permission.grp_tree%ROWTYPE;
6523     requestor_object    actor.usr%ROWTYPE;
6524     user_object        actor.usr%ROWTYPE;
6525     item_object        asset.copy%ROWTYPE;
6526     item_cn_object        asset.call_number%ROWTYPE;
6527     rec_descriptor        metabib.rec_descriptor%ROWTYPE;
6528     current_mp_weight    FLOAT;
6529     matchpoint_weight    FLOAT;
6530     tmp_weight        FLOAT;
6531     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
6532     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
6533 BEGIN
6534     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6535     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
6536     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6537     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6538     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
6539
6540     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
6541
6542     IF NOT FOUND THEN
6543         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
6544     ELSE
6545         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
6546     END IF;
6547
6548     LOOP 
6549         -- for each potential matchpoint for this ou and group ...
6550         FOR current_mp IN
6551             SELECT    m.*
6552               FROM    config.hold_matrix_matchpoint m
6553               WHERE    m.requestor_grp = current_requestor_group.id AND m.active
6554               ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
6555                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
6556                     CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
6557                     CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
6558                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
6559                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
6560
6561             current_mp_weight := 5.0;
6562
6563             IF current_mp.circ_modifier IS NOT NULL THEN
6564                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6565             END IF;
6566
6567             IF current_mp.marc_type IS NOT NULL THEN
6568                 IF item_object.circ_as_type IS NOT NULL THEN
6569                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6570                 ELSE
6571                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6572                 END IF;
6573             END IF;
6574
6575             IF current_mp.marc_form IS NOT NULL THEN
6576                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6577             END IF;
6578
6579             IF current_mp.marc_vr_format IS NOT NULL THEN
6580                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6581             END IF;
6582
6583             IF current_mp.juvenile_flag IS NOT NULL THEN
6584                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6585             END IF;
6586
6587             IF current_mp.ref_flag IS NOT NULL THEN
6588                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6589             END IF;
6590
6591
6592             -- caclulate the rule match weight
6593             IF current_mp.item_owning_ou IS NOT NULL THEN
6594                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
6595                 current_mp_weight := current_mp_weight - tmp_weight;
6596             END IF; 
6597
6598             IF current_mp.item_circ_ou IS NOT NULL THEN
6599                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
6600                 current_mp_weight := current_mp_weight - tmp_weight;
6601             END IF; 
6602
6603             IF current_mp.pickup_ou IS NOT NULL THEN
6604                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
6605                 current_mp_weight := current_mp_weight - tmp_weight;
6606             END IF; 
6607
6608             IF current_mp.request_ou IS NOT NULL THEN
6609                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
6610                 current_mp_weight := current_mp_weight - tmp_weight;
6611             END IF; 
6612
6613             IF current_mp.user_home_ou IS NOT NULL THEN
6614                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
6615                 current_mp_weight := current_mp_weight - tmp_weight;
6616             END IF; 
6617
6618             -- set the matchpoint if we found the best one
6619             IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
6620                 matchpoint = current_mp;
6621                 matchpoint_weight = current_mp_weight;
6622             END IF;
6623
6624         END LOOP;
6625
6626         EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6627
6628         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
6629     END LOOP;
6630
6631     RETURN matchpoint.id;
6632 END;
6633 $func$ LANGUAGE plpgsql;
6634
6635 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$
6636 DECLARE
6637     matchpoint_id        INT;
6638     user_object        actor.usr%ROWTYPE;
6639     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
6640     standing_penalty    config.standing_penalty%ROWTYPE;
6641     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
6642     transit_source        actor.org_unit%ROWTYPE;
6643     item_object        asset.copy%ROWTYPE;
6644     ou_skip              actor.org_unit_setting%ROWTYPE;
6645     result            action.matrix_test_result;
6646     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
6647     hold_count        INT;
6648     hold_transit_prox    INT;
6649     frozen_hold_count    INT;
6650     context_org_list    INT[];
6651     done            BOOL := FALSE;
6652 BEGIN
6653     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6654     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
6655
6656     result.success := TRUE;
6657
6658     -- Fail if we couldn't find a user
6659     IF user_object.id IS NULL THEN
6660         result.fail_part := 'no_user';
6661         result.success := FALSE;
6662         done := TRUE;
6663         RETURN NEXT result;
6664         RETURN;
6665     END IF;
6666
6667     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6668
6669     -- Fail if we couldn't find a copy
6670     IF item_object.id IS NULL THEN
6671         result.fail_part := 'no_item';
6672         result.success := FALSE;
6673         done := TRUE;
6674         RETURN NEXT result;
6675         RETURN;
6676     END IF;
6677
6678     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
6679     result.matchpoint := matchpoint_id;
6680
6681     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
6682
6683     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
6684     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
6685         result.fail_part := 'circ.holds.target_skip_me';
6686         result.success := FALSE;
6687         done := TRUE;
6688         RETURN NEXT result;
6689         RETURN;
6690     END IF;
6691
6692     -- Fail if user is barred
6693     IF user_object.barred IS TRUE THEN
6694         result.fail_part := 'actor.usr.barred';
6695         result.success := FALSE;
6696         done := TRUE;
6697         RETURN NEXT result;
6698         RETURN;
6699     END IF;
6700
6701     -- Fail if we couldn't find any matchpoint (requires a default)
6702     IF matchpoint_id IS NULL THEN
6703         result.fail_part := 'no_matchpoint';
6704         result.success := FALSE;
6705         done := TRUE;
6706         RETURN NEXT result;
6707         RETURN;
6708     END IF;
6709
6710     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
6711
6712     IF hold_test.holdable IS FALSE THEN
6713         result.fail_part := 'config.hold_matrix_test.holdable';
6714         result.success := FALSE;
6715         done := TRUE;
6716         RETURN NEXT result;
6717     END IF;
6718
6719     IF hold_test.transit_range IS NOT NULL THEN
6720         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
6721         IF hold_test.distance_is_from_owner THEN
6722             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;
6723         ELSE
6724             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
6725         END IF;
6726
6727         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
6728
6729         IF NOT FOUND THEN
6730             result.fail_part := 'transit_range';
6731             result.success := FALSE;
6732             done := TRUE;
6733             RETURN NEXT result;
6734         END IF;
6735     END IF;
6736  
6737     IF NOT retargetting THEN
6738         FOR standing_penalty IN
6739             SELECT  DISTINCT csp.*
6740               FROM  actor.usr_standing_penalty usp
6741                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6742               WHERE usr = match_user
6743                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6744                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6745                     AND csp.block_list LIKE '%HOLD%' LOOP
6746     
6747             result.fail_part := standing_penalty.name;
6748             result.success := FALSE;
6749             done := TRUE;
6750             RETURN NEXT result;
6751         END LOOP;
6752     
6753         IF hold_test.stop_blocked_user IS TRUE THEN
6754             FOR standing_penalty IN
6755                 SELECT  DISTINCT csp.*
6756                   FROM  actor.usr_standing_penalty usp
6757                         JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6758                   WHERE usr = match_user
6759                         AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6760                         AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6761                         AND csp.block_list LIKE '%CIRC%' LOOP
6762         
6763                 result.fail_part := standing_penalty.name;
6764                 result.success := FALSE;
6765                 done := TRUE;
6766                 RETURN NEXT result;
6767             END LOOP;
6768         END IF;
6769     
6770         IF hold_test.max_holds IS NOT NULL THEN
6771             SELECT    INTO hold_count COUNT(*)
6772               FROM    action.hold_request
6773               WHERE    usr = match_user
6774                 AND fulfillment_time IS NULL
6775                 AND cancel_time IS NULL
6776                 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
6777     
6778             IF hold_count >= hold_test.max_holds THEN
6779                 result.fail_part := 'config.hold_matrix_test.max_holds';
6780                 result.success := FALSE;
6781                 done := TRUE;
6782                 RETURN NEXT result;
6783             END IF;
6784         END IF;
6785     END IF;
6786
6787     IF item_object.age_protect IS NOT NULL THEN
6788         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
6789
6790         IF item_object.create_date + age_protect_object.age > NOW() THEN
6791             IF hold_test.distance_is_from_owner THEN
6792                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
6793             ELSE
6794                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
6795             END IF;
6796
6797             IF hold_transit_prox > age_protect_object.prox THEN
6798                 result.fail_part := 'config.rule_age_hold_protect.prox';
6799                 result.success := FALSE;
6800                 done := TRUE;
6801                 RETURN NEXT result;
6802             END IF;
6803         END IF;
6804     END IF;
6805
6806     IF NOT done THEN
6807         RETURN NEXT result;
6808     END IF;
6809
6810     RETURN;
6811 END;
6812 $func$ LANGUAGE plpgsql;
6813
6814 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$
6815     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE);
6816 $func$ LANGUAGE SQL;
6817
6818 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$
6819     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
6820 $func$ LANGUAGE SQL;
6821
6822 -- New post-delete trigger to propagate deletions to parent(s)
6823
6824 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
6825 BEGIN
6826
6827     -- Having deleted a renewal, we can delete the original circulation (or a previous
6828     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
6829     -- deletion of any prior parents, etc. recursively.
6830
6831     IF OLD.parent_circ IS NOT NULL THEN
6832         DELETE FROM action.circulation
6833         WHERE id = OLD.parent_circ;
6834     END IF;
6835
6836     RETURN OLD;
6837 END;
6838 $$ LANGUAGE 'plpgsql';
6839
6840 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
6841 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
6842
6843 -- This only gets inserted if there are no other id > 100 billing types
6844 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;
6845 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
6846
6847 -- Populate xact_type column in the materialized version of billable_xact_summary
6848
6849 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
6850 BEGIN
6851         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
6852                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
6853         RETURN NEW;
6854 END;
6855 $$ LANGUAGE PLPGSQL;
6856  
6857 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
6858 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
6859  
6860 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
6861 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
6862
6863 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
6864     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;
6865
6866 -- Generate the equivalent of compound subject entries from the existing rows
6867 -- so that we don't have to laboriously reindex them
6868
6869 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
6870 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
6871 --
6872 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
6873 --
6874 --INSERT INTO metabib.subject_field_entry (source, field, value)
6875 --    SELECT source, (
6876 --            SELECT id 
6877 --            FROM config.metabib_field
6878 --            WHERE field_class = 'subject' AND name = 'complete'
6879 --        ), 
6880 --        ARRAY_TO_STRING ( 
6881 --            ARRAY (
6882 --                SELECT value 
6883 --                FROM metabib.subject_field_entry msfe
6884 --                WHERE msfe.source = groupee.source
6885 --                ORDER BY source 
6886 --            ), ' ' 
6887 --        ) AS grouped
6888 --    FROM ( 
6889 --        SELECT source
6890 --        FROM metabib.subject_field_entry
6891 --        GROUP BY source
6892 --    ) AS groupee;
6893
6894 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
6895 DECLARE
6896         prev_billing    money.billing%ROWTYPE;
6897         old_billing     money.billing%ROWTYPE;
6898 BEGIN
6899         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
6900         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
6901
6902         IF OLD.id = old_billing.id THEN
6903                 UPDATE  money.materialized_billable_xact_summary
6904                   SET   last_billing_ts = prev_billing.billing_ts,
6905                         last_billing_note = prev_billing.note,
6906                         last_billing_type = prev_billing.billing_type
6907                   WHERE id = OLD.xact;
6908         END IF;
6909
6910         IF NOT OLD.voided THEN
6911                 UPDATE  money.materialized_billable_xact_summary
6912                   SET   total_owed = total_owed - OLD.amount,
6913                         balance_owed = balance_owed + OLD.amount
6914                   WHERE id = OLD.xact;
6915         END IF;
6916
6917         RETURN OLD;
6918 END;
6919 $$ LANGUAGE PLPGSQL;
6920
6921 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
6922 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
6923
6924 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
6925     use Unicode::Normalize;
6926     use Encode;
6927
6928     # When working with Unicode data, the first step is to decode it to
6929     # a byte string; after that, lowercasing is safe
6930     my $txt = lc(decode_utf8(shift));
6931     my $sf = shift;
6932
6933     $txt = NFD($txt);
6934     $txt =~ s/\pM+//go; # Remove diacritics
6935
6936     # remove non-combining diacritics
6937     # this list of characters follows the NACO normalization spec,
6938     # but a looser but more comprehensive version might be
6939     # $txt =~ s/\pLm+//go;
6940     $txt =~ tr/\x{02B9}\x{02BA}\x{02BB}\x{02BC}//d;
6941
6942     $txt =~ s/\xE6/AE/go;   # Convert ae digraph
6943     $txt =~ s/\x{153}/OE/go;# Convert oe digraph
6944     $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
6945
6946     $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
6947     $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
6948
6949     $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;     # Convert Latin and Greek
6950     $txt =~ tr/\x{2113}\xF0\x{111}\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LDD /; # Convert Misc
6951     $txt =~ tr/\'\[\]\|//d;                         # Remove Misc
6952
6953     if ($sf && $sf =~ /^a/o) {
6954         my $commapos = index($txt,',');
6955         if ($commapos > -1) {
6956             if ($commapos != length($txt) - 1) {
6957                 my @list = split /,/, $txt;
6958                 my $first = shift @list;
6959                 $txt = $first . ',' . join(' ', @list);
6960             } else {
6961                 $txt =~ s/,/ /go;
6962             }
6963         }
6964     } else {
6965         $txt =~ s/,/ /go;
6966     }
6967
6968     $txt =~ s/\s+/ /go; # Compress multiple spaces
6969     $txt =~ s/^\s+//o;  # Remove leading space
6970     $txt =~ s/\s+$//o;  # Remove trailing space
6971
6972     # Encoding the outgoing string is good practice, but not strictly
6973     # necessary in this case because we've stripped everything from it
6974     return encode_utf8($txt);
6975 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
6976
6977 -- Some handy functions, based on existing ones, to provide optional ingest normalization
6978
6979 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6980         SELECT SUBSTRING($1,$2);
6981 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6982
6983 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6984         SELECT SUBSTRING($1,1,$2);
6985 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6986
6987 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
6988         SELECT public.naco_normalize($1,'a');
6989 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6990
6991 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
6992         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
6993 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6994
6995 -- And ... a table in which to register them
6996
6997 CREATE TABLE config.index_normalizer (
6998         id              SERIAL  PRIMARY KEY,
6999         name            TEXT    UNIQUE NOT NULL,
7000         description     TEXT,
7001         func            TEXT    NOT NULL,
7002         param_count     INT     NOT NULL DEFAULT 0
7003 );
7004
7005 CREATE TABLE config.metabib_field_index_norm_map (
7006         id      SERIAL  PRIMARY KEY,
7007         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7008         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7009         params  TEXT,
7010         pos     INT     NOT NULL DEFAULT 0
7011 );
7012
7013 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7014         'NACO Normalize',
7015         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
7016         'naco_normalize',
7017         0
7018 );
7019
7020 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7021         'Normalize date range',
7022         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
7023         'split_date_range',
7024         1
7025 );
7026
7027 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7028         'NACO Normalize -- retain first comma',
7029         '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.',
7030         'naco_normalize_keep_comma',
7031         0
7032 );
7033
7034 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7035         'Strip Diacritics',
7036         'Convert text to NFD form and remove non-spacing combining marks.',
7037         'remove_diacritics',
7038         0
7039 );
7040
7041 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7042         'Up-case',
7043         'Convert text upper case.',
7044         'uppercase',
7045         0
7046 );
7047
7048 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7049         'Down-case',
7050         'Convert text lower case.',
7051         'lowercase',
7052         0
7053 );
7054
7055 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7056         'Extract Dewey-like number',
7057         'Extract a string of numeric characters ther resembles a DDC number.',
7058         'call_number_dewey',
7059         0
7060 );
7061
7062 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7063         'Left truncation',
7064         'Discard the specified number of characters from the left side of the string.',
7065         'left_trunc',
7066         1
7067 );
7068
7069 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7070         'Right truncation',
7071         'Include only the specified number of characters from the left side of the string.',
7072         'right_trunc',
7073         1
7074 );
7075
7076 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7077         'First word',
7078         'Include only the first space-separated word of a string.',
7079         'first_word',
7080         0
7081 );
7082
7083 INSERT INTO config.metabib_field_index_norm_map (field,norm)
7084         SELECT  m.id,
7085                 i.id
7086           FROM  config.metabib_field m,
7087                 config.index_normalizer i
7088           WHERE i.func IN ('naco_normalize','split_date_range');
7089
7090 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
7091 DECLARE
7092     normalizer      RECORD;
7093     value           TEXT := '';
7094 BEGIN
7095
7096     value := NEW.value;
7097
7098     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7099         FOR normalizer IN
7100             SELECT  n.func AS func,
7101                     n.param_count AS param_count,
7102                     m.params AS params
7103               FROM  config.index_normalizer n
7104                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7105               WHERE field = NEW.field AND m.pos < 0
7106               ORDER BY m.pos LOOP
7107                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7108                     quote_literal( value ) ||
7109                     CASE
7110                         WHEN normalizer.param_count > 0
7111                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7112                             ELSE ''
7113                         END ||
7114                     ')' INTO value;
7115
7116         END LOOP;
7117
7118         NEW.value := value;
7119     END IF;
7120
7121     IF NEW.index_vector = ''::tsvector THEN
7122         RETURN NEW;
7123     END IF;
7124
7125     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7126         FOR normalizer IN
7127             SELECT  n.func AS func,
7128                     n.param_count AS param_count,
7129                     m.params AS params
7130               FROM  config.index_normalizer n
7131                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7132               WHERE field = NEW.field AND m.pos >= 0
7133               ORDER BY m.pos LOOP
7134                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7135                     quote_literal( value ) ||
7136                     CASE
7137                         WHEN normalizer.param_count > 0
7138                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7139                             ELSE ''
7140                         END ||
7141                     ')' INTO value;
7142
7143         END LOOP;
7144     END IF;
7145
7146     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
7147         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
7148     ELSE
7149         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
7150     END IF;
7151
7152     RETURN NEW;
7153 END;
7154 $$ LANGUAGE PLPGSQL;
7155
7156 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7157
7158 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7159
7160 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7161     SELECT  ARRAY_TO_STRING(
7162                 oils_xpath(
7163                     $1 ||
7164                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
7165                     $2,
7166                     $4
7167                 ),
7168                 $3
7169             );
7170 $func$ LANGUAGE SQL IMMUTABLE;
7171
7172 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
7173     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
7174 $func$ LANGUAGE SQL IMMUTABLE;
7175
7176 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7177     SELECT oils_xpath_string( $1, $2, '', $3 );
7178 $func$ LANGUAGE SQL IMMUTABLE;
7179
7180 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
7181     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
7182 $func$ LANGUAGE SQL IMMUTABLE;
7183
7184 CREATE TYPE metabib.field_entry_template AS (
7185         field_class     TEXT,
7186         field           INT,
7187         source          BIGINT,
7188         value           TEXT
7189 );
7190
7191 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7192   use strict;
7193
7194   use XML::LibXSLT;
7195   use XML::LibXML;
7196
7197   my $doc = shift;
7198   my $xslt = shift;
7199
7200   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7201   # methods of parsing XML documents and stylesheets, in the hopes of broader
7202   # compatibility with distributions
7203   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7204
7205   # Cache the XML parser, if we do not already have one
7206   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7207     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7208
7209   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7210
7211   # Cache the XSLT processor, if we do not already have one
7212   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7213     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7214
7215   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7216     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7217
7218   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7219     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7220
7221   return $stylesheet->output_string(
7222     $stylesheet->transform(
7223       $parser->parse_string($doc)
7224     )
7225   );
7226
7227 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7228
7229 -- Add two columns so that the following function will compile.
7230 -- Eventually the label column will be NOT NULL, but not yet.
7231 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7232 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7233
7234 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7235 DECLARE
7236     bib     biblio.record_entry%ROWTYPE;
7237     idx     config.metabib_field%ROWTYPE;
7238     xfrm        config.xml_transform%ROWTYPE;
7239     prev_xfrm   TEXT;
7240     transformed_xml TEXT;
7241     xml_node    TEXT;
7242     xml_node_list   TEXT[];
7243     facet_text  TEXT;
7244     raw_text    TEXT;
7245     curr_text   TEXT;
7246     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7247     output_row  metabib.field_entry_template%ROWTYPE;
7248 BEGIN
7249
7250     -- Get the record
7251     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7252
7253     -- Loop over the indexing entries
7254     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7255
7256         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7257
7258         -- See if we can skip the XSLT ... it's expensive
7259         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7260             -- Can't skip the transform
7261             IF xfrm.xslt <> '---' THEN
7262                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7263             ELSE
7264                 transformed_xml := bib.marc;
7265             END IF;
7266
7267             prev_xfrm := xfrm.name;
7268         END IF;
7269
7270         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7271
7272         raw_text := NULL;
7273         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7274             CONTINUE WHEN xml_node !~ E'^\\s*<';
7275
7276             curr_text := ARRAY_TO_STRING(
7277                 oils_xpath( '//text()',
7278                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7279                         REGEXP_REPLACE( -- This escapes embeded <s
7280                             xml_node,
7281                             $re$(>[^<]+)(<)([^>]+<)$re$,
7282                             E'\\1&lt;\\3',
7283                             'g'
7284                         ),
7285                         '&(?!amp;)',
7286                         '&amp;',
7287                         'g'
7288                     )
7289                 ),
7290                 ' '
7291             );
7292
7293             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7294
7295             IF raw_text IS NOT NULL THEN
7296                 raw_text := raw_text || joiner;
7297             END IF;
7298
7299             raw_text := COALESCE(raw_text,'') || curr_text;
7300
7301             -- insert raw node text for faceting
7302             IF idx.facet_field THEN
7303
7304                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7305                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7306                 ELSE
7307                     facet_text := curr_text;
7308                 END IF;
7309
7310                 output_row.field_class = idx.field_class;
7311                 output_row.field = -1 * idx.id;
7312                 output_row.source = rid;
7313                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7314
7315                 RETURN NEXT output_row;
7316             END IF;
7317
7318         END LOOP;
7319
7320         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7321
7322         -- insert combined node text for searching
7323         IF idx.search_field THEN
7324             output_row.field_class = idx.field_class;
7325             output_row.field = idx.id;
7326             output_row.source = rid;
7327             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7328
7329             RETURN NEXT output_row;
7330         END IF;
7331
7332     END LOOP;
7333
7334 END;
7335 $func$ LANGUAGE PLPGSQL;
7336
7337 -- default to a space joiner
7338 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7339         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7340 $func$ LANGUAGE SQL;
7341
7342 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7343
7344 use MARC::Record;
7345 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7346
7347 my $xml = shift;
7348 my $r = MARC::Record->new_from_xml( $xml );
7349
7350 return_next( { tag => 'LDR', value => $r->leader } );
7351
7352 for my $f ( $r->fields ) {
7353     if ($f->is_control_field) {
7354         return_next({ tag => $f->tag, value => $f->data });
7355     } else {
7356         for my $s ($f->subfields) {
7357             return_next({
7358                 tag      => $f->tag,
7359                 ind1     => $f->indicator(1),
7360                 ind2     => $f->indicator(2),
7361                 subfield => $s->[0],
7362                 value    => $s->[1]
7363             });
7364
7365             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7366                 my $trim = $f->indicator(2) || 0;
7367                 return_next({
7368                     tag      => 'tnf',
7369                     ind1     => $f->indicator(1),
7370                     ind2     => $f->indicator(2),
7371                     subfield => 'a',
7372                     value    => substr( $s->[1], $trim )
7373                 });
7374             }
7375         }
7376     }
7377 }
7378
7379 return undef;
7380
7381 $func$ LANGUAGE PLPERLU;
7382
7383 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7384 DECLARE
7385     bib biblio.record_entry%ROWTYPE;
7386     output  metabib.full_rec%ROWTYPE;
7387     field   RECORD;
7388 BEGIN
7389     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7390
7391     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7392         output.record := rid;
7393         output.ind1 := field.ind1;
7394         output.ind2 := field.ind2;
7395         output.tag := field.tag;
7396         output.subfield := field.subfield;
7397         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7398             output.value := naco_normalize(field.value, field.subfield);
7399         ELSE
7400             output.value := field.value;
7401         END IF;
7402
7403         CONTINUE WHEN output.value IS NULL;
7404
7405         RETURN NEXT output;
7406     END LOOP;
7407 END;
7408 $func$ LANGUAGE PLPGSQL;
7409
7410 -- functions to create auditor objects
7411
7412 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7413 BEGIN
7414     EXECUTE $$
7415         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7416     $$;
7417         RETURN TRUE;
7418 END;
7419 $creator$ LANGUAGE 'plpgsql';
7420
7421 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7422 BEGIN
7423     EXECUTE $$
7424         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7425             audit_id    BIGINT                          PRIMARY KEY,
7426             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7427             audit_action        TEXT                            NOT NULL,
7428             LIKE $$ || sch || $$.$$ || tbl || $$
7429         );
7430     $$;
7431         RETURN TRUE;
7432 END;
7433 $creator$ LANGUAGE 'plpgsql';
7434
7435 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7436 BEGIN
7437     EXECUTE $$
7438         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7439         RETURNS TRIGGER AS $func$
7440         BEGIN
7441             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7442                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7443                     now(),
7444                     SUBSTR(TG_OP,1,1),
7445                     OLD.*;
7446             RETURN NULL;
7447         END;
7448         $func$ LANGUAGE 'plpgsql';
7449     $$;
7450         RETURN TRUE;
7451 END;
7452 $creator$ LANGUAGE 'plpgsql';
7453
7454 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7455 BEGIN
7456     EXECUTE $$
7457         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7458             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7459             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7460     $$;
7461         RETURN TRUE;
7462 END;
7463 $creator$ LANGUAGE 'plpgsql';
7464
7465 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7466 BEGIN
7467     EXECUTE $$
7468         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7469             SELECT      -1, now() as audit_time, '-' as audit_action, *
7470               FROM      $$ || sch || $$.$$ || tbl || $$
7471                 UNION ALL
7472             SELECT      *
7473               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7474     $$;
7475         RETURN TRUE;
7476 END;
7477 $creator$ LANGUAGE 'plpgsql';
7478
7479 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7480
7481 -- The main event
7482
7483 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7484 BEGIN
7485     PERFORM auditor.create_auditor_seq(sch, tbl);
7486     PERFORM auditor.create_auditor_history(sch, tbl);
7487     PERFORM auditor.create_auditor_func(sch, tbl);
7488     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7489     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7490     RETURN TRUE;
7491 END;
7492 $creator$ LANGUAGE 'plpgsql';
7493
7494 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7495
7496 ALTER TABLE action.hold_request
7497 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7498
7499 ALTER TABLE action.hold_request
7500 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7501
7502 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7503
7504 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7505
7506 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7507
7508 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7509
7510 -- Add claims_never_checked_out_count to actor.usr, related history
7511
7512 ALTER TABLE actor.usr ADD COLUMN
7513         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7514
7515 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7516         claims_never_checked_out_count INT;
7517
7518 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7519
7520 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7521
7522 -----------
7523
7524 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7525 BEGIN
7526         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7527                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7528                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7529                 END IF;
7530                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7531                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7532                 END IF;
7533                 IF NEW.stop_fines = 'LOST' THEN
7534                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7535                 END IF;
7536         END IF;
7537         RETURN NEW;
7538 END;
7539 $$ LANGUAGE 'plpgsql';
7540
7541 -- Create new table acq.fund_allocation_percent
7542 -- Populate it from acq.fund_allocation
7543 -- Convert all percentages to amounts in acq.fund_allocation
7544
7545 CREATE TABLE acq.fund_allocation_percent
7546 (
7547     id                   SERIAL            PRIMARY KEY,
7548     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7549                                                DEFERRABLE INITIALLY DEFERRED,
7550     org                  INT               NOT NULL REFERENCES actor.org_unit
7551                                                DEFERRABLE INITIALLY DEFERRED,
7552     fund_code            TEXT,
7553     percent              NUMERIC           NOT NULL,
7554     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7555                                                DEFERRABLE INITIALLY DEFERRED,
7556     note                 TEXT,
7557     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7558     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7559     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7560 );
7561
7562 -- Trigger function to validate combination of org_unit and fund_code
7563
7564 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7565 RETURNS TRIGGER AS $$
7566 --
7567 DECLARE
7568 --
7569 dummy int := 0;
7570 --
7571 BEGIN
7572     SELECT
7573         1
7574     INTO
7575         dummy
7576     FROM
7577         acq.fund
7578     WHERE
7579         org = NEW.org
7580         AND code = NEW.fund_code
7581         LIMIT 1;
7582     --
7583     IF dummy = 1 then
7584         RETURN NEW;
7585     ELSE
7586         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7587     END IF;
7588 END;
7589 $$ LANGUAGE plpgsql;
7590
7591 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7592     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7593     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7594
7595 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7596 RETURNS TRIGGER AS $$
7597 DECLARE
7598 --
7599 total_percent numeric;
7600 --
7601 BEGIN
7602     SELECT
7603         sum( percent )
7604     INTO
7605         total_percent
7606     FROM
7607         acq.fund_allocation_percent AS fap
7608     WHERE
7609         fap.funding_source = NEW.funding_source;
7610     --
7611     IF total_percent > 100 THEN
7612         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7613             NEW.funding_source;
7614     ELSE
7615         RETURN NEW;
7616     END IF;
7617 END;
7618 $$ LANGUAGE plpgsql;
7619
7620 CREATE TRIGGER acqfap_limit_100_trig
7621     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7622     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7623
7624 -- Populate new table from acq.fund_allocation
7625
7626 INSERT INTO acq.fund_allocation_percent
7627 (
7628     funding_source,
7629     org,
7630     fund_code,
7631     percent,
7632     allocator,
7633     note,
7634     create_time
7635 )
7636     SELECT
7637         fa.funding_source,
7638         fund.org,
7639         fund.code,
7640         fa.percent,
7641         fa.allocator,
7642         fa.note,
7643         fa.create_time
7644     FROM
7645         acq.fund_allocation AS fa
7646             INNER JOIN acq.fund AS fund
7647                 ON ( fa.fund = fund.id )
7648     WHERE
7649         fa.percent is not null
7650     ORDER BY
7651         fund.org;
7652
7653 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7654
7655 -- Algorithm to apply to each funding source:
7656
7657 -- 1. Add up the credits.
7658 -- 2. Add up the percentages.
7659 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7660 --    fractional cents from the result.  This is the total amount to be allocated.
7661 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7662 --    fractional cents to get a preliminary amount.
7663 -- 5. Add up the preliminary amounts for all the allocations.
7664 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7665 --    number of residual cents (resulting from having dropped fractional cents) that
7666 --    must be distributed across the funds in order to make the total of the amounts
7667 --    match the total allocation.
7668 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7669 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7670 --    for each successive fund, until all the residual cents have been exhausted.
7671
7672 -- Result: the sum of the individual allocations now equals the total to be allocated,
7673 -- to the penny.  The individual amounts match the percentages as closely as possible,
7674 -- given the constraint that the total must match.
7675
7676 CREATE OR REPLACE FUNCTION acq.apply_percents()
7677 RETURNS VOID AS $$
7678 declare
7679 --
7680 tot              RECORD;
7681 fund             RECORD;
7682 tot_cents        INTEGER;
7683 src              INTEGER;
7684 id               INTEGER[];
7685 curr_id          INTEGER;
7686 pennies          NUMERIC[];
7687 curr_amount      NUMERIC;
7688 i                INTEGER;
7689 total_of_floors  INTEGER;
7690 total_percent    NUMERIC;
7691 total_allocation INTEGER;
7692 residue          INTEGER;
7693 --
7694 begin
7695         RAISE NOTICE 'Applying percents';
7696         FOR tot IN
7697                 SELECT
7698                         fsrc.funding_source,
7699                         sum( fsrc.amount ) AS total
7700                 FROM
7701                         acq.funding_source_credit AS fsrc
7702                 WHERE fsrc.funding_source IN
7703                         ( SELECT DISTINCT fa.funding_source
7704                           FROM acq.fund_allocation AS fa
7705                           WHERE fa.percent IS NOT NULL )
7706                 GROUP BY
7707                         fsrc.funding_source
7708         LOOP
7709                 tot_cents = floor( tot.total * 100 );
7710                 src = tot.funding_source;
7711                 RAISE NOTICE 'Funding source % total %',
7712                         src, tot_cents;
7713                 i := 0;
7714                 total_of_floors := 0;
7715                 total_percent := 0;
7716                 --
7717                 FOR fund in
7718                         SELECT
7719                                 fa.id,
7720                                 fa.percent,
7721                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7722                         FROM
7723                                 acq.fund_allocation AS fa
7724                         WHERE
7725                                 fa.funding_source = src
7726                                 AND fa.percent IS NOT NULL
7727                         ORDER BY
7728                                 mod( fa.percent * tot_cents / 100, 1 ),
7729                                 fa.fund,
7730                                 fa.id
7731                 LOOP
7732                         RAISE NOTICE '   %: %',
7733                                 fund.id,
7734                                 fund.floor_pennies;
7735                         i := i + 1;
7736                         id[i] = fund.id;
7737                         pennies[i] = fund.floor_pennies;
7738                         total_percent := total_percent + fund.percent;
7739                         total_of_floors := total_of_floors + pennies[i];
7740                 END LOOP;
7741                 total_allocation := floor( total_percent * tot_cents /100 );
7742                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7743                 residue := total_allocation - total_of_floors;
7744                 RAISE NOTICE 'Residue: %', residue;
7745                 --
7746                 -- Post the calculated amounts, revising as needed to
7747                 -- distribute the rounding error
7748                 --
7749                 WHILE i > 0 LOOP
7750                         IF residue > 0 THEN
7751                                 pennies[i] = pennies[i] + 1;
7752                                 residue := residue - 1;
7753                         END IF;
7754                         --
7755                         -- Post amount
7756                         --
7757                         curr_id     := id[i];
7758                         curr_amount := trunc( pennies[i] / 100, 2 );
7759                         --
7760                         UPDATE
7761                                 acq.fund_allocation AS fa
7762                         SET
7763                                 amount = curr_amount,
7764                                 percent = NULL
7765                         WHERE
7766                                 fa.id = curr_id;
7767                         --
7768                         RAISE NOTICE '   ID % and amount %',
7769                                 curr_id,
7770                                 curr_amount;
7771                         i = i - 1;
7772                 END LOOP;
7773         END LOOP;
7774 end;
7775 $$ LANGUAGE 'plpgsql';
7776
7777 -- Run the temporary function
7778
7779 select * from acq.apply_percents();
7780
7781 -- Drop the temporary function now that we're done with it
7782
7783 DROP FUNCTION IF EXISTS acq.apply_percents();
7784
7785 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7786
7787 -- If the following step fails, it's probably because there are still some non-null percent values in
7788 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7789 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7790 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7791 -- slipped in afterwards.
7792
7793 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7794 -- procedure acq.apply_percents() as defined above.
7795
7796 ALTER TABLE acq.fund_allocation
7797 ALTER COLUMN amount SET NOT NULL;
7798
7799 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7800     SELECT  fund,
7801             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7802     FROM acq.fund_allocation a
7803          JOIN acq.fund f ON (a.fund = f.id)
7804          JOIN acq.funding_source s ON (a.funding_source = s.id)
7805     GROUP BY 1;
7806
7807 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7808     SELECT  funding_source,
7809             SUM(a.amount)::NUMERIC(100,2) AS amount
7810     FROM  acq.fund_allocation a
7811     GROUP BY 1;
7812
7813 ALTER TABLE acq.fund_allocation
7814 DROP COLUMN percent;
7815
7816 CREATE TABLE asset.copy_location_order
7817 (
7818         id              SERIAL           PRIMARY KEY,
7819         location        INT              NOT NULL
7820                                              REFERENCES asset.copy_location
7821                                              ON DELETE CASCADE
7822                                              DEFERRABLE INITIALLY DEFERRED,
7823         org             INT              NOT NULL
7824                                              REFERENCES actor.org_unit
7825                                              ON DELETE CASCADE
7826                                              DEFERRABLE INITIALLY DEFERRED,
7827         position        INT              NOT NULL DEFAULT 0,
7828         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7829 );
7830
7831 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7832
7833 -- If you ran this before its most recent incarnation:
7834 -- delete from config.upgrade_log where version = '0328';
7835 -- alter table money.credit_card_payment drop column cc_name;
7836
7837 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7838 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7839
7840 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$
7841 DECLARE
7842     current_group    permission.grp_tree%ROWTYPE;
7843     user_object    actor.usr%ROWTYPE;
7844     item_object    asset.copy%ROWTYPE;
7845     cn_object    asset.call_number%ROWTYPE;
7846     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7847     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7848     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7849 BEGIN
7850     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7851     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7852     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7853     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7854     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7855
7856     LOOP 
7857         -- for each potential matchpoint for this ou and group ...
7858         FOR current_mp IN
7859             SELECT  m.*
7860               FROM  config.circ_matrix_matchpoint m
7861                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
7862                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
7863               WHERE m.grp = current_group.id
7864                     AND m.active
7865                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
7866                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
7867               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
7868                     CASE WHEN m.copy_owning_lib IS NOT NULL
7869                         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 )
7870                         ELSE 0
7871                     END +
7872                     CASE WHEN m.copy_circ_lib IS NOT NULL
7873                         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 )
7874                         ELSE 0
7875                     END +
7876                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
7877                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
7878                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
7879                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
7880                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
7881                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
7882                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
7883                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
7884                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
7885
7886             IF current_mp.is_renewal IS NOT NULL THEN
7887                 CONTINUE WHEN current_mp.is_renewal <> renewal;
7888             END IF;
7889
7890             IF current_mp.circ_modifier IS NOT NULL THEN
7891                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
7892             END IF;
7893
7894             IF current_mp.marc_type IS NOT NULL THEN
7895                 IF item_object.circ_as_type IS NOT NULL THEN
7896                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
7897                 ELSE
7898                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
7899                 END IF;
7900             END IF;
7901
7902             IF current_mp.marc_form IS NOT NULL THEN
7903                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
7904             END IF;
7905
7906             IF current_mp.marc_vr_format IS NOT NULL THEN
7907                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
7908             END IF;
7909
7910             IF current_mp.ref_flag IS NOT NULL THEN
7911                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
7912             END IF;
7913
7914             IF current_mp.juvenile_flag IS NOT NULL THEN
7915                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
7916             END IF;
7917
7918             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
7919                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
7920             END IF;
7921
7922             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
7923                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
7924             END IF;
7925
7926
7927             -- everything was undefined or matched
7928             matchpoint = current_mp;
7929
7930             EXIT WHEN matchpoint.id IS NOT NULL;
7931         END LOOP;
7932
7933         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
7934
7935         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
7936     END LOOP;
7937
7938     RETURN matchpoint;
7939 END;
7940 $func$ LANGUAGE plpgsql;
7941
7942 CREATE TYPE action.hold_stats AS (
7943     hold_count              INT,
7944     copy_count              INT,
7945     available_count         INT,
7946     total_copy_ratio        FLOAT,
7947     available_copy_ratio    FLOAT
7948 );
7949
7950 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
7951 DECLARE
7952     output          action.hold_stats%ROWTYPE;
7953     hold_count      INT := 0;
7954     copy_count      INT := 0;
7955     available_count INT := 0;
7956     hold_map_data   RECORD;
7957 BEGIN
7958
7959     output.hold_count := 0;
7960     output.copy_count := 0;
7961     output.available_count := 0;
7962
7963     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
7964       FROM  action.hold_copy_map m
7965             JOIN action.hold_request h ON (m.hold = h.id)
7966       WHERE m.target_copy = copy_id
7967             AND NOT h.frozen;
7968
7969     output.hold_count := hold_count;
7970
7971     IF output.hold_count > 0 THEN
7972         FOR hold_map_data IN
7973             SELECT  DISTINCT m.target_copy,
7974                     acp.status
7975               FROM  action.hold_copy_map m
7976                     JOIN asset.copy acp ON (m.target_copy = acp.id)
7977                     JOIN action.hold_request h ON (m.hold = h.id)
7978               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
7979         LOOP
7980             output.copy_count := output.copy_count + 1;
7981             IF hold_map_data.status IN (0,7,12) THEN
7982                 output.available_count := output.available_count + 1;
7983             END IF;
7984         END LOOP;
7985         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
7986         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
7987
7988     END IF;
7989
7990     RETURN output;
7991
7992 END;
7993 $func$ LANGUAGE PLPGSQL;
7994
7995 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
7996 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
7997
7998 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
7999
8000 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8001 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8002
8003 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
8004     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
8005     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
8006     copy_owning_lib
8007 );
8008
8009 -- Return the correct fail_part when the item can't be found
8010 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$
8011 DECLARE
8012     user_object        actor.usr%ROWTYPE;
8013     standing_penalty    config.standing_penalty%ROWTYPE;
8014     item_object        asset.copy%ROWTYPE;
8015     item_status_object    config.copy_status%ROWTYPE;
8016     item_location_object    asset.copy_location%ROWTYPE;
8017     result            action.matrix_test_result;
8018     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
8019     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
8020     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
8021     hold_ratio          action.hold_stats%ROWTYPE;
8022     penalty_type         TEXT;
8023     tmp_grp         INT;
8024     items_out        INT;
8025     context_org_list        INT[];
8026     done            BOOL := FALSE;
8027 BEGIN
8028     result.success := TRUE;
8029
8030     -- Fail if the user is BARRED
8031     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8032
8033     -- Fail if we couldn't find the user 
8034     IF user_object.id IS NULL THEN
8035         result.fail_part := 'no_user';
8036         result.success := FALSE;
8037         done := TRUE;
8038         RETURN NEXT result;
8039         RETURN;
8040     END IF;
8041
8042     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
8043
8044     -- Fail if we couldn't find the item 
8045     IF item_object.id IS NULL THEN
8046         result.fail_part := 'no_item';
8047         result.success := FALSE;
8048         done := TRUE;
8049         RETURN NEXT result;
8050         RETURN;
8051     END IF;
8052
8053     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
8054     result.matchpoint := circ_test.id;
8055
8056     -- Fail if we couldn't find a matchpoint
8057     IF result.matchpoint IS NULL THEN
8058         result.fail_part := 'no_matchpoint';
8059         result.success := FALSE;
8060         done := TRUE;
8061         RETURN NEXT result;
8062     END IF;
8063
8064     IF user_object.barred IS TRUE THEN
8065         result.fail_part := 'actor.usr.barred';
8066         result.success := FALSE;
8067         done := TRUE;
8068         RETURN NEXT result;
8069     END IF;
8070
8071     -- Fail if the item can't circulate
8072     IF item_object.circulate IS FALSE THEN
8073         result.fail_part := 'asset.copy.circulate';
8074         result.success := FALSE;
8075         done := TRUE;
8076         RETURN NEXT result;
8077     END IF;
8078
8079     -- Fail if the item isn't in a circulateable status on a non-renewal
8080     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
8081         result.fail_part := 'asset.copy.status';
8082         result.success := FALSE;
8083         done := TRUE;
8084         RETURN NEXT result;
8085     ELSIF renewal AND item_object.status <> 1 THEN
8086         result.fail_part := 'asset.copy.status';
8087         result.success := FALSE;
8088         done := TRUE;
8089         RETURN NEXT result;
8090     END IF;
8091
8092     -- Fail if the item can't circulate because of the shelving location
8093     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8094     IF item_location_object.circulate IS FALSE THEN
8095         result.fail_part := 'asset.copy_location.circulate';
8096         result.success := FALSE;
8097         done := TRUE;
8098         RETURN NEXT result;
8099     END IF;
8100
8101     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
8102
8103     -- Fail if the test is set to hard non-circulating
8104     IF circ_test.circulate IS FALSE THEN
8105         result.fail_part := 'config.circ_matrix_test.circulate';
8106         result.success := FALSE;
8107         done := TRUE;
8108         RETURN NEXT result;
8109     END IF;
8110
8111     -- Fail if the total copy-hold ratio is too low
8112     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
8113         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8114         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
8115             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8116             result.success := FALSE;
8117             done := TRUE;
8118             RETURN NEXT result;
8119         END IF;
8120     END IF;
8121
8122     -- Fail if the available copy-hold ratio is too low
8123     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
8124         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8125         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
8126             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8127             result.success := FALSE;
8128             done := TRUE;
8129             RETURN NEXT result;
8130         END IF;
8131     END IF;
8132
8133     IF renewal THEN
8134         penalty_type = '%RENEW%';
8135     ELSE
8136         penalty_type = '%CIRC%';
8137     END IF;
8138
8139     FOR standing_penalty IN
8140         SELECT  DISTINCT csp.*
8141           FROM  actor.usr_standing_penalty usp
8142                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8143           WHERE usr = match_user
8144                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8145                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8146                 AND csp.block_list LIKE penalty_type LOOP
8147
8148         result.fail_part := standing_penalty.name;
8149         result.success := FALSE;
8150         done := TRUE;
8151         RETURN NEXT result;
8152     END LOOP;
8153
8154     -- Fail if the user has too many items with specific circ_modifiers checked out
8155     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
8156         SELECT  INTO items_out COUNT(*)
8157           FROM  action.circulation circ
8158             JOIN asset.copy cp ON (cp.id = circ.target_copy)
8159           WHERE circ.usr = match_user
8160                AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
8161             AND circ.checkin_time IS NULL
8162             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8163             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);
8164         IF items_out >= out_by_circ_mod.items_out THEN
8165             result.fail_part := 'config.circ_matrix_circ_mod_test';
8166             result.success := FALSE;
8167             done := TRUE;
8168             RETURN NEXT result;
8169         END IF;
8170     END LOOP;
8171
8172     -- If we passed everything, return the successful matchpoint id
8173     IF NOT done THEN
8174         RETURN NEXT result;
8175     END IF;
8176
8177     RETURN;
8178 END;
8179 $func$ LANGUAGE plpgsql;
8180
8181 CREATE TABLE config.remote_account (
8182     id          SERIAL  PRIMARY KEY,
8183     label       TEXT    NOT NULL,
8184     host        TEXT    NOT NULL,   -- name or IP, :port optional
8185     username    TEXT,               -- optional, since we could default to $USER
8186     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
8187     account     TEXT,               -- aka profile or FTP "account" command
8188     path        TEXT,               -- aka directory
8189     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8190     last_activity TIMESTAMP WITH TIME ZONE
8191 );
8192
8193 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8194     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8195     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8196         vendcode    TEXT,
8197         vendacct    TEXT
8198
8199 ) INHERITS (config.remote_account);
8200
8201 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8202
8203 CREATE TABLE acq.claim_type (
8204         id             SERIAL           PRIMARY KEY,
8205         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8206                                                  DEFERRABLE INITIALLY DEFERRED,
8207         code           TEXT             NOT NULL,
8208         description    TEXT             NOT NULL,
8209         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8210 );
8211
8212 CREATE TABLE acq.claim (
8213         id             SERIAL           PRIMARY KEY,
8214         type           INT              NOT NULL REFERENCES acq.claim_type
8215                                                  DEFERRABLE INITIALLY DEFERRED,
8216         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8217                                                  DEFERRABLE INITIALLY DEFERRED
8218 );
8219
8220 CREATE TABLE acq.claim_policy (
8221         id              SERIAL       PRIMARY KEY,
8222         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8223                                      DEFERRABLE INITIALLY DEFERRED,
8224         name            TEXT         NOT NULL,
8225         description     TEXT         NOT NULL,
8226         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8227 );
8228
8229 -- Add a san column for EDI. 
8230 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8231
8232 ALTER TABLE acq.provider ADD COLUMN san INT;
8233
8234 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8235
8236 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8237 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8238
8239 ALTER TABLE acq.provider
8240         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8241
8242 ALTER TABLE acq.provider
8243         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8244
8245 ALTER TABLE acq.provider
8246         ADD COLUMN url TEXT;
8247
8248 ALTER TABLE acq.provider
8249         ADD COLUMN email TEXT;
8250
8251 ALTER TABLE acq.provider
8252         ADD COLUMN phone TEXT;
8253
8254 ALTER TABLE acq.provider
8255         ADD COLUMN fax_phone TEXT;
8256
8257 ALTER TABLE acq.provider
8258         ADD COLUMN default_claim_policy INT
8259                 REFERENCES acq.claim_policy
8260                 DEFERRABLE INITIALLY DEFERRED;
8261
8262 ALTER TABLE action.transit_copy
8263 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8264                                                          DEFERRABLE INITIALLY DEFERRED;
8265
8266 DROP SCHEMA IF EXISTS booking CASCADE;
8267
8268 CREATE SCHEMA booking;
8269
8270 CREATE TABLE booking.resource_type (
8271         id             SERIAL          PRIMARY KEY,
8272         name           TEXT            NOT NULL,
8273         fine_interval  INTERVAL,
8274         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
8275         owner          INT             NOT NULL
8276                                        REFERENCES actor.org_unit( id )
8277                                        DEFERRABLE INITIALLY DEFERRED,
8278         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
8279         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
8280     record         BIGINT          REFERENCES biblio.record_entry (id)
8281                                        DEFERRABLE INITIALLY DEFERRED,
8282     max_fine       NUMERIC(8,2),
8283     elbow_room     INTERVAL,
8284     CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
8285 );
8286
8287 CREATE TABLE booking.resource (
8288         id             SERIAL           PRIMARY KEY,
8289         owner          INT              NOT NULL
8290                                         REFERENCES actor.org_unit(id)
8291                                         DEFERRABLE INITIALLY DEFERRED,
8292         type           INT              NOT NULL
8293                                         REFERENCES booking.resource_type(id)
8294                                         DEFERRABLE INITIALLY DEFERRED,
8295         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
8296         barcode        TEXT             NOT NULL,
8297         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
8298         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8299         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8300         CONSTRAINT br_unique UNIQUE (owner, barcode)
8301 );
8302
8303 -- For non-catalog items: hijack barcode for name/description
8304
8305 CREATE TABLE booking.resource_attr (
8306         id              SERIAL          PRIMARY KEY,
8307         owner           INT             NOT NULL
8308                                         REFERENCES actor.org_unit(id)
8309                                         DEFERRABLE INITIALLY DEFERRED,
8310         name            TEXT            NOT NULL,
8311         resource_type   INT             NOT NULL
8312                                         REFERENCES booking.resource_type(id)
8313                                         ON DELETE CASCADE
8314                                         DEFERRABLE INITIALLY DEFERRED,
8315         required        BOOLEAN         NOT NULL DEFAULT FALSE,
8316         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
8317 );
8318
8319 CREATE TABLE booking.resource_attr_value (
8320         id               SERIAL         PRIMARY KEY,
8321         owner            INT            NOT NULL
8322                                         REFERENCES actor.org_unit(id)
8323                                         DEFERRABLE INITIALLY DEFERRED,
8324         attr             INT            NOT NULL
8325                                         REFERENCES booking.resource_attr(id)
8326                                         DEFERRABLE INITIALLY DEFERRED,
8327         valid_value      TEXT           NOT NULL,
8328         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
8329 );
8330
8331 CREATE TABLE booking.resource_attr_map (
8332         id               SERIAL         PRIMARY KEY,
8333         resource         INT            NOT NULL
8334                                         REFERENCES booking.resource(id)
8335                                         ON DELETE CASCADE
8336                                         DEFERRABLE INITIALLY DEFERRED,
8337         resource_attr    INT            NOT NULL
8338                                         REFERENCES booking.resource_attr(id)
8339                                         ON DELETE CASCADE
8340                                         DEFERRABLE INITIALLY DEFERRED,
8341         value            INT            NOT NULL
8342                                         REFERENCES booking.resource_attr_value(id)
8343                                         DEFERRABLE INITIALLY DEFERRED,
8344         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
8345 );
8346
8347 CREATE TABLE booking.reservation (
8348         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
8349         start_time       TIMESTAMPTZ,
8350         end_time         TIMESTAMPTZ,
8351         capture_time     TIMESTAMPTZ,
8352         cancel_time      TIMESTAMPTZ,
8353         pickup_time      TIMESTAMPTZ,
8354         return_time      TIMESTAMPTZ,
8355         booking_interval INTERVAL,
8356         fine_interval    INTERVAL,
8357         fine_amount      DECIMAL(8,2),
8358         target_resource_type  INT       NOT NULL
8359                                         REFERENCES booking.resource_type(id)
8360                                         ON DELETE CASCADE
8361                                         DEFERRABLE INITIALLY DEFERRED,
8362         target_resource  INT            REFERENCES booking.resource(id)
8363                                         ON DELETE CASCADE
8364                                         DEFERRABLE INITIALLY DEFERRED,
8365         current_resource INT            REFERENCES booking.resource(id)
8366                                         ON DELETE CASCADE
8367                                         DEFERRABLE INITIALLY DEFERRED,
8368         request_lib      INT            NOT NULL
8369                                         REFERENCES actor.org_unit(id)
8370                                         DEFERRABLE INITIALLY DEFERRED,
8371         pickup_lib       INT            REFERENCES actor.org_unit(id)
8372                                         DEFERRABLE INITIALLY DEFERRED,
8373         capture_staff    INT            REFERENCES actor.usr(id)
8374                                         DEFERRABLE INITIALLY DEFERRED,
8375     max_fine         NUMERIC(8,2)
8376 ) INHERITS (money.billable_xact);
8377
8378 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
8379
8380 ALTER TABLE booking.reservation
8381         ADD CONSTRAINT booking_reservation_usr_fkey
8382         FOREIGN KEY (usr) REFERENCES actor.usr (id)
8383         DEFERRABLE INITIALLY DEFERRED;
8384
8385 CREATE TABLE booking.reservation_attr_value_map (
8386         id               SERIAL         PRIMARY KEY,
8387         reservation      INT            NOT NULL
8388                                         REFERENCES booking.reservation(id)
8389                                         ON DELETE CASCADE
8390                                         DEFERRABLE INITIALLY DEFERRED,
8391         attr_value       INT            NOT NULL
8392                                         REFERENCES booking.resource_attr_value(id)
8393                                         ON DELETE CASCADE
8394                                         DEFERRABLE INITIALLY DEFERRED,
8395         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
8396 );
8397
8398 -- represents a circ chain summary
8399 CREATE TYPE action.circ_chain_summary AS (
8400     num_circs INTEGER,
8401     start_time TIMESTAMP WITH TIME ZONE,
8402     checkout_workstation TEXT,
8403     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8404     last_stop_fines TEXT,
8405     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8406     last_renewal_workstation TEXT, -- NULL if no renewals
8407     last_checkin_workstation TEXT,
8408     last_checkin_time TIMESTAMP WITH TIME ZONE,
8409     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8410 );
8411
8412 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8413 DECLARE
8414     tmp_circ action.circulation%ROWTYPE;
8415     circ_0 action.circulation%ROWTYPE;
8416 BEGIN
8417
8418     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8419
8420     IF tmp_circ IS NULL THEN
8421         RETURN NEXT tmp_circ;
8422     END IF;
8423     circ_0 := tmp_circ;
8424
8425     -- find the front of the chain
8426     WHILE TRUE LOOP
8427         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8428         IF tmp_circ IS NULL THEN
8429             EXIT;
8430         END IF;
8431         circ_0 := tmp_circ;
8432     END LOOP;
8433
8434     -- now send the circs to the caller, oldest to newest
8435     tmp_circ := circ_0;
8436     WHILE TRUE LOOP
8437         IF tmp_circ IS NULL THEN
8438             EXIT;
8439         END IF;
8440         RETURN NEXT tmp_circ;
8441         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8442     END LOOP;
8443
8444 END;
8445 $$ LANGUAGE 'plpgsql';
8446
8447 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8448
8449 DECLARE
8450
8451     -- first circ in the chain
8452     circ_0 action.circulation%ROWTYPE;
8453
8454     -- last circ in the chain
8455     circ_n action.circulation%ROWTYPE;
8456
8457     -- circ chain under construction
8458     chain action.circ_chain_summary;
8459     tmp_circ action.circulation%ROWTYPE;
8460
8461 BEGIN
8462     
8463     chain.num_circs := 0;
8464     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8465
8466         IF chain.num_circs = 0 THEN
8467             circ_0 := tmp_circ;
8468         END IF;
8469
8470         chain.num_circs := chain.num_circs + 1;
8471         circ_n := tmp_circ;
8472     END LOOP;
8473
8474     chain.start_time := circ_0.xact_start;
8475     chain.last_stop_fines := circ_n.stop_fines;
8476     chain.last_stop_fines_time := circ_n.stop_fines_time;
8477     chain.last_checkin_time := circ_n.checkin_time;
8478     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8479     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8480     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8481
8482     IF chain.num_circs > 1 THEN
8483         chain.last_renewal_time := circ_n.xact_start;
8484         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8485     END IF;
8486
8487     RETURN chain;
8488
8489 END;
8490 $$ LANGUAGE 'plpgsql';
8491
8492 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8493 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8494 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8495
8496 ALTER TABLE config.standing_penalty
8497         ADD COLUMN org_depth   INTEGER;
8498
8499 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8500 DECLARE
8501     user_object         actor.usr%ROWTYPE;
8502     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8503     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8504     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8505     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8506     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8507     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8508     tmp_grp             INT;
8509     items_overdue       INT;
8510     items_out           INT;
8511     context_org_list    INT[];
8512     current_fines        NUMERIC(8,2) := 0.0;
8513     tmp_fines            NUMERIC(8,2);
8514     tmp_groc            RECORD;
8515     tmp_circ            RECORD;
8516     tmp_org             actor.org_unit%ROWTYPE;
8517     tmp_penalty         config.standing_penalty%ROWTYPE;
8518     tmp_depth           INTEGER;
8519 BEGIN
8520     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8521
8522     -- Max fines
8523     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8524
8525     -- Fail if the user has a high fine balance
8526     LOOP
8527         tmp_grp := user_object.profile;
8528         LOOP
8529             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8530
8531             IF max_fines.threshold IS NULL THEN
8532                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8533             ELSE
8534                 EXIT;
8535             END IF;
8536
8537             IF tmp_grp IS NULL THEN
8538                 EXIT;
8539             END IF;
8540         END LOOP;
8541
8542         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8543             EXIT;
8544         END IF;
8545
8546         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8547
8548     END LOOP;
8549
8550     IF max_fines.threshold IS NOT NULL THEN
8551
8552         RETURN QUERY
8553             SELECT  *
8554               FROM  actor.usr_standing_penalty
8555               WHERE usr = match_user
8556                     AND org_unit = max_fines.org_unit
8557                     AND (stop_date IS NULL or stop_date > NOW())
8558                     AND standing_penalty = 1;
8559
8560         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8561
8562         SELECT  SUM(f.balance_owed) INTO current_fines
8563           FROM  money.materialized_billable_xact_summary f
8564                 JOIN (
8565                     SELECT  r.id
8566                       FROM  booking.reservation r
8567                       WHERE r.usr = match_user
8568                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8569                             AND xact_finish IS NULL
8570                                 UNION ALL
8571                     SELECT  g.id
8572                       FROM  money.grocery g
8573                       WHERE g.usr = match_user
8574                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8575                             AND xact_finish IS NULL
8576                                 UNION ALL
8577                     SELECT  circ.id
8578                       FROM  action.circulation circ
8579                       WHERE circ.usr = match_user
8580                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8581                             AND xact_finish IS NULL ) l USING (id);
8582
8583         IF current_fines >= max_fines.threshold THEN
8584             new_sp_row.usr := match_user;
8585             new_sp_row.org_unit := max_fines.org_unit;
8586             new_sp_row.standing_penalty := 1;
8587             RETURN NEXT new_sp_row;
8588         END IF;
8589     END IF;
8590
8591     -- Start over for max overdue
8592     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8593
8594     -- Fail if the user has too many overdue items
8595     LOOP
8596         tmp_grp := user_object.profile;
8597         LOOP
8598
8599             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8600
8601             IF max_overdue.threshold IS NULL THEN
8602                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8603             ELSE
8604                 EXIT;
8605             END IF;
8606
8607             IF tmp_grp IS NULL THEN
8608                 EXIT;
8609             END IF;
8610         END LOOP;
8611
8612         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8613             EXIT;
8614         END IF;
8615
8616         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8617
8618     END LOOP;
8619
8620     IF max_overdue.threshold IS NOT NULL THEN
8621
8622         RETURN QUERY
8623             SELECT  *
8624               FROM  actor.usr_standing_penalty
8625               WHERE usr = match_user
8626                     AND org_unit = max_overdue.org_unit
8627                     AND (stop_date IS NULL or stop_date > NOW())
8628                     AND standing_penalty = 2;
8629
8630         SELECT  INTO items_overdue COUNT(*)
8631           FROM  action.circulation circ
8632                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8633           WHERE circ.usr = match_user
8634             AND circ.checkin_time IS NULL
8635             AND circ.due_date < NOW()
8636             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8637
8638         IF items_overdue >= max_overdue.threshold::INT THEN
8639             new_sp_row.usr := match_user;
8640             new_sp_row.org_unit := max_overdue.org_unit;
8641             new_sp_row.standing_penalty := 2;
8642             RETURN NEXT new_sp_row;
8643         END IF;
8644     END IF;
8645
8646     -- Start over for max out
8647     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8648
8649     -- Fail if the user has too many checked out items
8650     LOOP
8651         tmp_grp := user_object.profile;
8652         LOOP
8653             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8654
8655             IF max_items_out.threshold IS NULL THEN
8656                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8657             ELSE
8658                 EXIT;
8659             END IF;
8660
8661             IF tmp_grp IS NULL THEN
8662                 EXIT;
8663             END IF;
8664         END LOOP;
8665
8666         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8667             EXIT;
8668         END IF;
8669
8670         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8671
8672     END LOOP;
8673
8674
8675     -- Fail if the user has too many items checked out
8676     IF max_items_out.threshold IS NOT NULL THEN
8677
8678         RETURN QUERY
8679             SELECT  *
8680               FROM  actor.usr_standing_penalty
8681               WHERE usr = match_user
8682                     AND org_unit = max_items_out.org_unit
8683                     AND (stop_date IS NULL or stop_date > NOW())
8684                     AND standing_penalty = 3;
8685
8686         SELECT  INTO items_out COUNT(*)
8687           FROM  action.circulation circ
8688                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8689           WHERE circ.usr = match_user
8690                 AND circ.checkin_time IS NULL
8691                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8692
8693            IF items_out >= max_items_out.threshold::INT THEN
8694             new_sp_row.usr := match_user;
8695             new_sp_row.org_unit := max_items_out.org_unit;
8696             new_sp_row.standing_penalty := 3;
8697             RETURN NEXT new_sp_row;
8698            END IF;
8699     END IF;
8700
8701     -- Start over for collections warning
8702     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8703
8704     -- Fail if the user has a collections-level fine balance
8705     LOOP
8706         tmp_grp := user_object.profile;
8707         LOOP
8708             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8709
8710             IF max_fines.threshold IS NULL THEN
8711                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8712             ELSE
8713                 EXIT;
8714             END IF;
8715
8716             IF tmp_grp IS NULL THEN
8717                 EXIT;
8718             END IF;
8719         END LOOP;
8720
8721         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8722             EXIT;
8723         END IF;
8724
8725         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8726
8727     END LOOP;
8728
8729     IF max_fines.threshold IS NOT NULL THEN
8730
8731         RETURN QUERY
8732             SELECT  *
8733               FROM  actor.usr_standing_penalty
8734               WHERE usr = match_user
8735                     AND org_unit = max_fines.org_unit
8736                     AND (stop_date IS NULL or stop_date > NOW())
8737                     AND standing_penalty = 4;
8738
8739         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8740
8741         SELECT  SUM(f.balance_owed) INTO current_fines
8742           FROM  money.materialized_billable_xact_summary f
8743                 JOIN (
8744                     SELECT  r.id
8745                       FROM  booking.reservation r
8746                       WHERE r.usr = match_user
8747                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8748                             AND r.xact_finish IS NULL
8749                                 UNION ALL
8750                     SELECT  g.id
8751                       FROM  money.grocery g
8752                       WHERE g.usr = match_user
8753                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8754                             AND g.xact_finish IS NULL
8755                                 UNION ALL
8756                     SELECT  circ.id
8757                       FROM  action.circulation circ
8758                       WHERE circ.usr = match_user
8759                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8760                             AND circ.xact_finish IS NULL ) l USING (id);
8761
8762         IF current_fines >= max_fines.threshold THEN
8763             new_sp_row.usr := match_user;
8764             new_sp_row.org_unit := max_fines.org_unit;
8765             new_sp_row.standing_penalty := 4;
8766             RETURN NEXT new_sp_row;
8767         END IF;
8768     END IF;
8769
8770     -- Start over for in collections
8771     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8772
8773     -- Remove the in-collections penalty if the user has paid down enough
8774     -- This penalty is different, because this code is not responsible for creating 
8775     -- new in-collections penalties, only for removing them
8776     LOOP
8777         tmp_grp := user_object.profile;
8778         LOOP
8779             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8780
8781             IF max_fines.threshold IS NULL THEN
8782                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8783             ELSE
8784                 EXIT;
8785             END IF;
8786
8787             IF tmp_grp IS NULL THEN
8788                 EXIT;
8789             END IF;
8790         END LOOP;
8791
8792         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8793             EXIT;
8794         END IF;
8795
8796         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8797
8798     END LOOP;
8799
8800     IF max_fines.threshold IS NOT NULL THEN
8801
8802         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8803
8804         -- first, see if the user had paid down to the threshold
8805         SELECT  SUM(f.balance_owed) INTO current_fines
8806           FROM  money.materialized_billable_xact_summary f
8807                 JOIN (
8808                     SELECT  r.id
8809                       FROM  booking.reservation r
8810                       WHERE r.usr = match_user
8811                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8812                             AND r.xact_finish IS NULL
8813                                 UNION ALL
8814                     SELECT  g.id
8815                       FROM  money.grocery g
8816                       WHERE g.usr = match_user
8817                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8818                             AND g.xact_finish IS NULL
8819                                 UNION ALL
8820                     SELECT  circ.id
8821                       FROM  action.circulation circ
8822                       WHERE circ.usr = match_user
8823                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8824                             AND circ.xact_finish IS NULL ) l USING (id);
8825
8826         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8827             -- patron has paid down enough
8828
8829             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8830
8831             IF tmp_penalty.org_depth IS NOT NULL THEN
8832
8833                 -- since this code is not responsible for applying the penalty, it can't 
8834                 -- guarantee the current context org will match the org at which the penalty 
8835                 --- was applied.  search up the org tree until we hit the configured penalty depth
8836                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8837                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8838
8839                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8840
8841                     RETURN QUERY
8842                         SELECT  *
8843                           FROM  actor.usr_standing_penalty
8844                           WHERE usr = match_user
8845                                 AND org_unit = tmp_org.id
8846                                 AND (stop_date IS NULL or stop_date > NOW())
8847                                 AND standing_penalty = 30;
8848
8849                     IF tmp_org.parent_ou IS NULL THEN
8850                         EXIT;
8851                     END IF;
8852
8853                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8854                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8855                 END LOOP;
8856
8857             ELSE
8858
8859                 -- no penalty depth is defined, look for exact matches
8860
8861                 RETURN QUERY
8862                     SELECT  *
8863                       FROM  actor.usr_standing_penalty
8864                       WHERE usr = match_user
8865                             AND org_unit = max_fines.org_unit
8866                             AND (stop_date IS NULL or stop_date > NOW())
8867                             AND standing_penalty = 30;
8868             END IF;
8869     
8870         END IF;
8871
8872     END IF;
8873
8874     RETURN;
8875 END;
8876 $func$ LANGUAGE plpgsql;
8877
8878 -- Create a default row in acq.fiscal_calendar
8879 -- Add a column in actor.org_unit to point to it
8880
8881 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8882
8883 ALTER TABLE actor.org_unit
8884 ADD COLUMN fiscal_calendar INT NOT NULL
8885         REFERENCES acq.fiscal_calendar( id )
8886         DEFERRABLE INITIALLY DEFERRED
8887         DEFAULT 1;
8888
8889 ALTER TABLE auditor.actor_org_unit_history
8890         ADD COLUMN fiscal_calendar INT;
8891
8892 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8893
8894 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8895
8896 ALTER TABLE acq.funding_source_credit
8897 ADD COLUMN deadline_date TIMESTAMPTZ;
8898
8899 ALTER TABLE acq.funding_source_credit
8900 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8901
8902 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8903
8904 CREATE TABLE acq.fund_transfer (
8905         id               SERIAL         PRIMARY KEY,
8906         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8907                                         DEFERRABLE INITIALLY DEFERRED,
8908         src_amount       NUMERIC        NOT NULL,
8909         dest_fund        INT            REFERENCES acq.fund( id )
8910                                         DEFERRABLE INITIALLY DEFERRED,
8911         dest_amount      NUMERIC,
8912         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
8913         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
8914                                         DEFERRABLE INITIALLY DEFERRED,
8915         note             TEXT,
8916     funding_source_credit INTEGER   NOT NULL
8917                                         REFERENCES acq.funding_source_credit(id)
8918                                         DEFERRABLE INITIALLY DEFERRED
8919 );
8920
8921 CREATE INDEX acqftr_usr_idx
8922 ON acq.fund_transfer( transfer_user );
8923
8924 COMMENT ON TABLE acq.fund_transfer IS $$
8925 /*
8926  * Copyright (C) 2009  Georgia Public Library Service
8927  * Scott McKellar <scott@esilibrary.com>
8928  *
8929  * Fund Transfer
8930  *
8931  * Each row represents the transfer of money from a source fund
8932  * to a destination fund.  There should be corresponding entries
8933  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
8934  * to record how much money moved from which fund to which other
8935  * fund.
8936  * 
8937  * The presence of two amount fields, rather than one, reflects
8938  * the possibility that the two funds are denominated in different
8939  * currencies.  If they use the same currency type, the two
8940  * amounts should be the same.
8941  *
8942  * ****
8943  *
8944  * This program is free software; you can redistribute it and/or
8945  * modify it under the terms of the GNU General Public License
8946  * as published by the Free Software Foundation; either version 2
8947  * of the License, or (at your option) any later version.
8948  *
8949  * This program is distributed in the hope that it will be useful,
8950  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8951  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
8952  * GNU General Public License for more details.
8953  */
8954 $$;
8955
8956 CREATE TABLE acq.claim_event_type (
8957         id             SERIAL           PRIMARY KEY,
8958         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8959                                                  DEFERRABLE INITIALLY DEFERRED,
8960         code           TEXT             NOT NULL,
8961         description    TEXT             NOT NULL,
8962         library_initiated BOOL          NOT NULL DEFAULT FALSE,
8963         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
8964 );
8965
8966 CREATE TABLE acq.claim_event (
8967         id             BIGSERIAL        PRIMARY KEY,
8968         type           INT              NOT NULL REFERENCES acq.claim_event_type
8969                                                  DEFERRABLE INITIALLY DEFERRED,
8970         claim          SERIAL           NOT NULL REFERENCES acq.claim
8971                                                  DEFERRABLE INITIALLY DEFERRED,
8972         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
8973         creator        INT              NOT NULL REFERENCES actor.usr
8974                                                  DEFERRABLE INITIALLY DEFERRED,
8975         note           TEXT
8976 );
8977
8978 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
8979
8980 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
8981         src_usr  IN INTEGER,
8982         dest_usr IN INTEGER
8983 ) RETURNS VOID AS $$
8984 DECLARE
8985         suffix TEXT;
8986         renamable_row RECORD;
8987 BEGIN
8988
8989         UPDATE actor.usr SET
8990                 active = FALSE,
8991                 card = NULL,
8992                 mailing_address = NULL,
8993                 billing_address = NULL
8994         WHERE id = src_usr;
8995
8996         -- acq.*
8997         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
8998         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
8999         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
9000         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
9001         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
9002         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
9003         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
9004
9005         -- Update with a rename to avoid collisions
9006         FOR renamable_row in
9007                 SELECT id, name
9008                 FROM   acq.picklist
9009                 WHERE  owner = src_usr
9010         LOOP
9011                 suffix := ' (' || src_usr || ')';
9012                 LOOP
9013                         BEGIN
9014                                 UPDATE  acq.picklist
9015                                 SET     owner = dest_usr, name = name || suffix
9016                                 WHERE   id = renamable_row.id;
9017                         EXCEPTION WHEN unique_violation THEN
9018                                 suffix := suffix || ' ';
9019                                 CONTINUE;
9020                         END;
9021                         EXIT;
9022                 END LOOP;
9023         END LOOP;
9024
9025         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
9026         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
9027         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
9028         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
9029         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
9030         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
9031         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
9032         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
9033
9034         -- action.*
9035         DELETE FROM action.circulation WHERE usr = src_usr;
9036         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
9037         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
9038         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
9039         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
9040         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
9041         DELETE FROM action.hold_request WHERE usr = src_usr;
9042         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
9043         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
9044         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
9045         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
9046         DELETE FROM action.survey_response WHERE usr = src_usr;
9047         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
9048
9049         -- actor.*
9050         DELETE FROM actor.card WHERE usr = src_usr;
9051         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
9052
9053         -- The following update is intended to avoid transient violations of a foreign
9054         -- key constraint, whereby actor.usr_address references itself.  It may not be
9055         -- necessary, but it does no harm.
9056         UPDATE actor.usr_address SET replaces = NULL
9057                 WHERE usr = src_usr AND replaces IS NOT NULL;
9058         DELETE FROM actor.usr_address WHERE usr = src_usr;
9059         DELETE FROM actor.usr_note WHERE usr = src_usr;
9060         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
9061         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
9062         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
9063         DELETE FROM actor.usr_setting WHERE usr = src_usr;
9064         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
9065         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
9066
9067         -- asset.*
9068         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
9069         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
9070         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
9071         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
9072         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
9073         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
9074
9075         -- auditor.*
9076         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
9077         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
9078         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
9079         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
9080         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
9081         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
9082         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
9083         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
9084
9085         -- biblio.*
9086         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
9087         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
9088         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
9089         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
9090
9091         -- container.*
9092         -- Update buckets with a rename to avoid collisions
9093         FOR renamable_row in
9094                 SELECT id, name
9095                 FROM   container.biblio_record_entry_bucket
9096                 WHERE  owner = src_usr
9097         LOOP
9098                 suffix := ' (' || src_usr || ')';
9099                 LOOP
9100                         BEGIN
9101                                 UPDATE  container.biblio_record_entry_bucket
9102                                 SET     owner = dest_usr, name = name || suffix
9103                                 WHERE   id = renamable_row.id;
9104                         EXCEPTION WHEN unique_violation THEN
9105                                 suffix := suffix || ' ';
9106                                 CONTINUE;
9107                         END;
9108                         EXIT;
9109                 END LOOP;
9110         END LOOP;
9111
9112         FOR renamable_row in
9113                 SELECT id, name
9114                 FROM   container.call_number_bucket
9115                 WHERE  owner = src_usr
9116         LOOP
9117                 suffix := ' (' || src_usr || ')';
9118                 LOOP
9119                         BEGIN
9120                                 UPDATE  container.call_number_bucket
9121                                 SET     owner = dest_usr, name = name || suffix
9122                                 WHERE   id = renamable_row.id;
9123                         EXCEPTION WHEN unique_violation THEN
9124                                 suffix := suffix || ' ';
9125                                 CONTINUE;
9126                         END;
9127                         EXIT;
9128                 END LOOP;
9129         END LOOP;
9130
9131         FOR renamable_row in
9132                 SELECT id, name
9133                 FROM   container.copy_bucket
9134                 WHERE  owner = src_usr
9135         LOOP
9136                 suffix := ' (' || src_usr || ')';
9137                 LOOP
9138                         BEGIN
9139                                 UPDATE  container.copy_bucket
9140                                 SET     owner = dest_usr, name = name || suffix
9141                                 WHERE   id = renamable_row.id;
9142                         EXCEPTION WHEN unique_violation THEN
9143                                 suffix := suffix || ' ';
9144                                 CONTINUE;
9145                         END;
9146                         EXIT;
9147                 END LOOP;
9148         END LOOP;
9149
9150         FOR renamable_row in
9151                 SELECT id, name
9152                 FROM   container.user_bucket
9153                 WHERE  owner = src_usr
9154         LOOP
9155                 suffix := ' (' || src_usr || ')';
9156                 LOOP
9157                         BEGIN
9158                                 UPDATE  container.user_bucket
9159                                 SET     owner = dest_usr, name = name || suffix
9160                                 WHERE   id = renamable_row.id;
9161                         EXCEPTION WHEN unique_violation THEN
9162                                 suffix := suffix || ' ';
9163                                 CONTINUE;
9164                         END;
9165                         EXIT;
9166                 END LOOP;
9167         END LOOP;
9168
9169         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
9170
9171         -- money.*
9172         DELETE FROM money.billable_xact WHERE usr = src_usr;
9173         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9174         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9175
9176         -- permission.*
9177         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9178         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9179         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9180         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9181
9182         -- reporter.*
9183         -- Update with a rename to avoid collisions
9184         BEGIN
9185                 FOR renamable_row in
9186                         SELECT id, name
9187                         FROM   reporter.output_folder
9188                         WHERE  owner = src_usr
9189                 LOOP
9190                         suffix := ' (' || src_usr || ')';
9191                         LOOP
9192                                 BEGIN
9193                                         UPDATE  reporter.output_folder
9194                                         SET     owner = dest_usr, name = name || suffix
9195                                         WHERE   id = renamable_row.id;
9196                                 EXCEPTION WHEN unique_violation THEN
9197                                         suffix := suffix || ' ';
9198                                         CONTINUE;
9199                                 END;
9200                                 EXIT;
9201                         END LOOP;
9202                 END LOOP;
9203         EXCEPTION WHEN undefined_table THEN
9204                 -- do nothing
9205         END;
9206
9207         BEGIN
9208                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9209         EXCEPTION WHEN undefined_table THEN
9210                 -- do nothing
9211         END;
9212
9213         -- Update with a rename to avoid collisions
9214         BEGIN
9215                 FOR renamable_row in
9216                         SELECT id, name
9217                         FROM   reporter.report_folder
9218                         WHERE  owner = src_usr
9219                 LOOP
9220                         suffix := ' (' || src_usr || ')';
9221                         LOOP
9222                                 BEGIN
9223                                         UPDATE  reporter.report_folder
9224                                         SET     owner = dest_usr, name = name || suffix
9225                                         WHERE   id = renamable_row.id;
9226                                 EXCEPTION WHEN unique_violation THEN
9227                                         suffix := suffix || ' ';
9228                                         CONTINUE;
9229                                 END;
9230                                 EXIT;
9231                         END LOOP;
9232                 END LOOP;
9233         EXCEPTION WHEN undefined_table THEN
9234                 -- do nothing
9235         END;
9236
9237         BEGIN
9238                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9239         EXCEPTION WHEN undefined_table THEN
9240                 -- do nothing
9241         END;
9242
9243         BEGIN
9244                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9245         EXCEPTION WHEN undefined_table THEN
9246                 -- do nothing
9247         END;
9248
9249         -- Update with a rename to avoid collisions
9250         BEGIN
9251                 FOR renamable_row in
9252                         SELECT id, name
9253                         FROM   reporter.template_folder
9254                         WHERE  owner = src_usr
9255                 LOOP
9256                         suffix := ' (' || src_usr || ')';
9257                         LOOP
9258                                 BEGIN
9259                                         UPDATE  reporter.template_folder
9260                                         SET     owner = dest_usr, name = name || suffix
9261                                         WHERE   id = renamable_row.id;
9262                                 EXCEPTION WHEN unique_violation THEN
9263                                         suffix := suffix || ' ';
9264                                         CONTINUE;
9265                                 END;
9266                                 EXIT;
9267                         END LOOP;
9268                 END LOOP;
9269         EXCEPTION WHEN undefined_table THEN
9270         -- do nothing
9271         END;
9272
9273         -- vandelay.*
9274         -- Update with a rename to avoid collisions
9275         FOR renamable_row in
9276                 SELECT id, name
9277                 FROM   vandelay.queue
9278                 WHERE  owner = src_usr
9279         LOOP
9280                 suffix := ' (' || src_usr || ')';
9281                 LOOP
9282                         BEGIN
9283                                 UPDATE  vandelay.queue
9284                                 SET     owner = dest_usr, name = name || suffix
9285                                 WHERE   id = renamable_row.id;
9286                         EXCEPTION WHEN unique_violation THEN
9287                                 suffix := suffix || ' ';
9288                                 CONTINUE;
9289                         END;
9290                         EXIT;
9291                 END LOOP;
9292         END LOOP;
9293
9294 END;
9295 $$ LANGUAGE plpgsql;
9296
9297 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9298 /**
9299  * Finds rows dependent on a given row in actor.usr and either deletes them
9300  * or reassigns them to a different user.
9301  */
9302 $$;
9303
9304 CREATE OR REPLACE FUNCTION actor.usr_delete(
9305         src_usr  IN INTEGER,
9306         dest_usr IN INTEGER
9307 ) RETURNS VOID AS $$
9308 DECLARE
9309         old_profile actor.usr.profile%type;
9310         old_home_ou actor.usr.home_ou%type;
9311         new_profile actor.usr.profile%type;
9312         new_home_ou actor.usr.home_ou%type;
9313         new_name    text;
9314         new_dob     actor.usr.dob%type;
9315 BEGIN
9316         SELECT
9317                 id || '-PURGED-' || now(),
9318                 profile,
9319                 home_ou,
9320                 dob
9321         INTO
9322                 new_name,
9323                 old_profile,
9324                 old_home_ou,
9325                 new_dob
9326         FROM
9327                 actor.usr
9328         WHERE
9329                 id = src_usr;
9330         --
9331         -- Quit if no such user
9332         --
9333         IF old_profile IS NULL THEN
9334                 RETURN;
9335         END IF;
9336         --
9337         perform actor.usr_purge_data( src_usr, dest_usr );
9338         --
9339         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9340         -- could assume that there is only one root.  Theoretically, someday, maybe,
9341         -- there could be multiple roots, so we take extra trouble to get the right ones.
9342         --
9343         SELECT
9344                 id
9345         INTO
9346                 new_profile
9347         FROM
9348                 permission.grp_ancestors( old_profile )
9349         WHERE
9350                 parent is null;
9351         --
9352         SELECT
9353                 id
9354         INTO
9355                 new_home_ou
9356         FROM
9357                 actor.org_unit_ancestors( old_home_ou )
9358         WHERE
9359                 parent_ou is null;
9360         --
9361         -- Truncate date of birth
9362         --
9363         IF new_dob IS NOT NULL THEN
9364                 new_dob := date_trunc( 'year', new_dob );
9365         END IF;
9366         --
9367         UPDATE
9368                 actor.usr
9369                 SET
9370                         card = NULL,
9371                         profile = new_profile,
9372                         usrname = new_name,
9373                         email = NULL,
9374                         passwd = random()::text,
9375                         standing = DEFAULT,
9376                         ident_type = 
9377                         (
9378                                 SELECT MIN( id )
9379                                 FROM config.identification_type
9380                         ),
9381                         ident_value = NULL,
9382                         ident_type2 = NULL,
9383                         ident_value2 = NULL,
9384                         net_access_level = DEFAULT,
9385                         photo_url = NULL,
9386                         prefix = NULL,
9387                         first_given_name = new_name,
9388                         second_given_name = NULL,
9389                         family_name = new_name,
9390                         suffix = NULL,
9391                         alias = NULL,
9392                         day_phone = NULL,
9393                         evening_phone = NULL,
9394                         other_phone = NULL,
9395                         mailing_address = NULL,
9396                         billing_address = NULL,
9397                         home_ou = new_home_ou,
9398                         dob = new_dob,
9399                         active = FALSE,
9400                         master_account = DEFAULT, 
9401                         super_user = DEFAULT,
9402                         barred = FALSE,
9403                         deleted = TRUE,
9404                         juvenile = DEFAULT,
9405                         usrgroup = 0,
9406                         claims_returned_count = DEFAULT,
9407                         credit_forward_balance = DEFAULT,
9408                         last_xact_id = DEFAULT,
9409                         alert_message = NULL,
9410                         create_date = now(),
9411                         expire_date = now()
9412         WHERE
9413                 id = src_usr;
9414 END;
9415 $$ LANGUAGE plpgsql;
9416
9417 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9418 /**
9419  * Logically deletes a user.  Removes personally identifiable information,
9420  * and purges associated data in other tables.
9421  */
9422 $$;
9423
9424 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9425
9426 ALTER TABLE acq.fund
9427 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9428
9429 ALTER TABLE acq.fund
9430         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9431
9432 -- A fund can't roll over if it doesn't propagate from one year to the next
9433
9434 ALTER TABLE acq.fund
9435         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9436         ( propagate OR NOT rollover );
9437
9438 ALTER TABLE acq.fund
9439         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9440
9441 ALTER TABLE acq.fund
9442     ADD COLUMN balance_warning_percent INT
9443     CONSTRAINT balance_warning_percent_limit
9444         CHECK( balance_warning_percent <= 100 );
9445
9446 ALTER TABLE acq.fund
9447     ADD COLUMN balance_stop_percent INT
9448     CONSTRAINT balance_stop_percent_limit
9449         CHECK( balance_stop_percent <= 100 );
9450
9451 CREATE VIEW acq.ordered_funding_source_credit AS
9452         SELECT
9453                 CASE WHEN deadline_date IS NULL THEN
9454                         2
9455                 ELSE
9456                         1
9457                 END AS sort_priority,
9458                 CASE WHEN deadline_date IS NULL THEN
9459                         effective_date
9460                 ELSE
9461                         deadline_date
9462                 END AS sort_date,
9463                 id,
9464                 funding_source,
9465                 amount,
9466                 note
9467         FROM
9468                 acq.funding_source_credit;
9469
9470 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9471 /*
9472  * Copyright (C) 2009  Georgia Public Library Service
9473  * Scott McKellar <scott@gmail.com>
9474  *
9475  * The acq.ordered_funding_source_credit view is a prioritized
9476  * ordering of funding source credits.  When ordered by the first
9477  * three columns, this view defines the order in which the various
9478  * credits are to be tapped for spending, subject to the allocations
9479  * in the acq.fund_allocation table.
9480  *
9481  * The first column reflects the principle that we should spend
9482  * money with deadlines before spending money without deadlines.
9483  *
9484  * The second column reflects the principle that we should spend the
9485  * oldest money first.  For money with deadlines, that means that we
9486  * spend first from the credit with the earliest deadline.  For
9487  * money without deadlines, we spend first from the credit with the
9488  * earliest effective date.  
9489  *
9490  * The third column is a tie breaker to ensure a consistent
9491  * ordering.
9492  *
9493  * ****
9494  *
9495  * This program is free software; you can redistribute it and/or
9496  * modify it under the terms of the GNU General Public License
9497  * as published by the Free Software Foundation; either version 2
9498  * of the License, or (at your option) any later version.
9499  *
9500  * This program is distributed in the hope that it will be useful,
9501  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9502  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9503  * GNU General Public License for more details.
9504  */
9505 $$;
9506
9507 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9508     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9509       FROM  money.materialized_billable_xact_summary m
9510             LEFT JOIN action.circulation c ON (c.id = m.id)
9511             LEFT JOIN money.grocery g ON (g.id = m.id)
9512             LEFT JOIN booking.reservation r ON (r.id = m.id);
9513
9514 CREATE TABLE config.marc21_rec_type_map (
9515     code        TEXT    PRIMARY KEY,
9516     type_val    TEXT    NOT NULL,
9517     blvl_val    TEXT    NOT NULL
9518 );
9519
9520 CREATE TABLE config.marc21_ff_pos_map (
9521     id          SERIAL  PRIMARY KEY,
9522     fixed_field TEXT    NOT NULL,
9523     tag         TEXT    NOT NULL,
9524     rec_type    TEXT    NOT NULL,
9525     start_pos   INT     NOT NULL,
9526     length      INT     NOT NULL,
9527     default_val TEXT    NOT NULL DEFAULT ' '
9528 );
9529
9530 CREATE TABLE config.marc21_physical_characteristic_type_map (
9531     ptype_key   TEXT    PRIMARY KEY,
9532     label       TEXT    NOT NULL -- I18N
9533 );
9534
9535 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9536     id          SERIAL  PRIMARY KEY,
9537     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9538     subfield    TEXT    NOT NULL,
9539     start_pos   INT     NOT NULL,
9540     length      INT     NOT NULL,
9541     label       TEXT    NOT NULL -- I18N
9542 );
9543
9544 CREATE TABLE config.marc21_physical_characteristic_value_map (
9545     id              SERIAL  PRIMARY KEY,
9546     value           TEXT    NOT NULL,
9547     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9548     label           TEXT    NOT NULL -- I18N
9549 );
9550
9551 ----------------------------------
9552 -- MARC21 record structure data --
9553 ----------------------------------
9554
9555 -- Record type map
9556 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9557 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9558 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9559 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9560 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9561 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9562 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9563 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9564
9565 ------ Physical Characteristics
9566
9567 -- Map
9568 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9569 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9570 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9571 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9572 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9573 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9574 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9575 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');
9576 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9577 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9578 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9579 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9580 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9581 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');
9582 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9583 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9584 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9585 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9586 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9587 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9588 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9589 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9590 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9591 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9592 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');
9593 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');
9594 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');
9595 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');
9596 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9597 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');
9598 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9599 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9600 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9601 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');
9602 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9603 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9604 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
9605 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');
9606 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9607 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');
9608 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
9609 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9610 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9611 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9612 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9613 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9614 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9615 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');
9616
9617 -- Electronic Resource
9618 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9619 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9620 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');
9621 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');
9622 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');
9623 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');
9624 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');
9625 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');
9626 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');
9627 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');
9628 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9629 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9630 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9631 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
9632 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');
9633 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');
9634 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9635 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');
9636 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9637 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');
9638 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9639 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9640 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9641 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.');
9642 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.');
9643 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.');
9644 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.');
9645 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.');
9646 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');
9647 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.');
9648 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9649 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.');
9650 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9651 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
9652 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)');
9653 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
9654 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9655 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9656 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9657 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9658 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');
9659 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9660 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');
9661 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');
9662 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9663 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9664 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9665 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');
9666 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
9667 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9668 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
9669 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');
9670 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');
9671 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');
9672 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)');
9673 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9674 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');
9675 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9676 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9677 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9678 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9679 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9680 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9681 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9682 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
9683 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9684 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');
9685 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9686 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9687 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9688
9689 -- Globe
9690 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9691 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9692 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');
9693 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');
9694 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');
9695 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');
9696 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9697 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9698 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
9699 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');
9700 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9701 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9702 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9703 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9704 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9705 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9706 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9707 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9708 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9709 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9710 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9711 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9712 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9713 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
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
9718 -- Tactile Material
9719 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9720 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9721 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9722 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9723 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9724 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');
9725 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9726 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9727 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9728 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');
9729 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');
9730 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');
9731 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');
9732 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');
9733 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');
9734 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9735 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9736 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9737 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9738 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9739 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9740 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9741 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9742 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9743 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9744 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9745 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');
9746 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');
9747 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');
9748 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9749 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');
9750 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');
9751 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');
9752 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');
9753 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');
9754 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');
9755 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9756 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');
9757 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');
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_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9760 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
9761 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');
9762 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');
9763 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');
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 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9766
9767 -- Projected Graphic
9768 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9769 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9770 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');
9771 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9772 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');
9773 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');
9774 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9775 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
9776 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9777 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9778 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');
9779 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9780 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');
9781 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9782 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');
9783 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9784 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9785 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9786 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9787 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9788 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');
9789 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');
9790 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');
9791 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
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 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');
9795 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');
9796 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');
9797 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9798 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9799 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');
9800 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');
9801 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');
9802 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');
9803 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');
9804 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');
9805 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');
9806 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9807 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9808 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9809 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9810 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9811 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.');
9812 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.');
9813 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.');
9814 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.');
9815 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.');
9816 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.');
9817 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.');
9818 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.)');
9819 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.)');
9820 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.)');
9821 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.)');
9822 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9823 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.)');
9824 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.)');
9825 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.)');
9826 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.)');
9827 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9828 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9829 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9830 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9831 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9832 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9833 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');
9834 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');
9835 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');
9836 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9837 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9838
9839 -- Microform
9840 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9841 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9842 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');
9843 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');
9844 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');
9845 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');
9846 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9847 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');
9848 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9849 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9850 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9851 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9853 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9854 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9855 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9856 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
9857 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.');
9858 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.');
9859 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.');
9860 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9861 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.');
9862 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.)');
9863 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.)');
9864 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.)');
9865 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.)');
9866 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9867 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9868 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');
9869 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)');
9870 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)');
9871 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)');
9872 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)');
9873 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-)');
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_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Reduction ratio varies');
9876 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9877 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');
9878 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9879 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9880 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9881 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9882 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9883 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');
9884 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9885 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9886 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9887 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9888 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9889 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9890 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
9891 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');
9892 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');
9893 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');
9894 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');
9895 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9896 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9897 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');
9898 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');
9899 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');
9900 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');
9901 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed base');
9902 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');
9903 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');
9904 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');
9905 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');
9906 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9907 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9908
9909 -- Non-projected Graphic
9910 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9911 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
9912 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
9913 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
9914 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
9915 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');
9916 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
9917 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
9918 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
9919 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
9920 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');
9921 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
9922 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');
9923 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9924 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9925 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
9926 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');
9927 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');
9928 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9929 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');
9930 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9931 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9932 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9933 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
9934 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9935 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');
9936 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');
9937 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9938 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9939 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9940 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9941 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9942 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9943 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9944 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9945 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9946 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9947 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9948 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9949 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9950 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9951 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
9952 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9953 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');
9954 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');
9955 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9956 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9957 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9958 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9959 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9960 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');
9961 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9962 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9963 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9964 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9965 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9966 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9967 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9968 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9969
9970 -- Motion Picture
9971 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
9972 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
9973 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');
9974 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');
9975 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');
9976 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9977 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9978 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
9979 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');
9980 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9981 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');
9982 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
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 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
9986 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');
9987 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)');
9988 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
9989 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)');
9990 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');
9991 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');
9992 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9993 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9994 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');
9995 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');
9996 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');
9997 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9998 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
9999 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');
10000 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');
10001 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');
10002 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');
10003 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');
10004 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');
10005 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');
10006 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10007 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
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 ('m','h','7','1','Dimensions');
10011 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.');
10012 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.');
10013 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.');
10014 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.');
10015 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.');
10016 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.');
10017 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.');
10018 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10019 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10020 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
10021 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10022 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10023 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');
10024 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');
10025 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
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 ('m','j','9','1','Production elements');
10029 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');
10030 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
10031 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
10032 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
10033 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');
10034 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');
10035 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');
10036 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');
10037 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10038
10039 -- Remote-sensing Image
10040 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
10041 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
10042 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10043 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
10044 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
10045 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
10046 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
10047 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');
10048 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10049 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10050 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
10051 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Low oblique');
10052 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');
10053 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
10054 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10055 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10056 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
10057 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%');
10058 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%');
10059 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%');
10060 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%');
10061 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%');
10062 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%');
10063 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%');
10064 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%');
10065 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%');
10066 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%');
10067 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');
10068 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10069 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
10070 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
10071 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');
10072 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');
10073 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');
10074 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');
10075 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');
10076 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');
10077 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');
10078 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');
10079 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');
10080 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10081 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10082 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
10083 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
10084 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');
10085 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');
10086 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');
10087 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');
10088 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10089 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10090 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
10091 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
10092 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
10093 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10094 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10095 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
10096 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');
10097 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');
10098 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');
10099 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');
10100 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');
10101 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)');
10102 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');
10103 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
10104 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');
10105 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)');
10106 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)');
10107 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)');
10108 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');
10109 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');
10110 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');
10111 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');
10112 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');
10113 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');
10114 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');
10115 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');
10116 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');
10117 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');
10118 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');
10119 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');
10120 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');
10121 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');
10122 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');
10123 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');
10124 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');
10125 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');
10126 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');
10127 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');
10128 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');
10129 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)');
10130 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');
10131 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
10132 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
10133 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');
10134 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');
10135 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10136 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10137
10138 -- Sound Recording
10139 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
10140 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
10141 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');
10142 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
10143 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');
10144 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');
10145 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
10146 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');
10147 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');
10148 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10149 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');
10150 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10151 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
10152 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');
10153 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');
10154 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');
10155 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');
10156 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');
10157 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');
10158 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');
10159 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');
10160 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');
10161 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');
10162 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');
10163 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');
10164 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');
10165 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');
10166 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10167 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10168 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
10169 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10170 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
10171 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10172 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10173 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10174 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10175 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');
10176 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');
10177 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');
10178 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10179 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10180 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10181 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.');
10182 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.');
10183 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.');
10184 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.');
10185 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.');
10186 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.');
10187 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.)');
10188 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.');
10189 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');
10190 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.');
10191 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.');
10192 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10193 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10194 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
10195 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.');
10196 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.');
10197 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');
10198 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.');
10199 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.');
10200 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10201 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10202 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
10203 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');
10204 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');
10205 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');
10206 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');
10207 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');
10208 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');
10209 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');
10210 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10211 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10212 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10213 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');
10214 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');
10215 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');
10216 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');
10217 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');
10218 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');
10219 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');
10220 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');
10221 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');
10222 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10223 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10224 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
10225 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');
10226 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');
10227 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');
10228 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');
10229 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10230 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10231
10232 -- Videorecording
10233 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10234 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10235 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10236 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10237 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10238 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10239 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10240 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10241 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10242 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');
10243 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10244 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10245 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');
10246 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10247 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10248 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10249 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10250 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10251 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');
10252 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10253 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');
10254 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10255 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10256 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10257 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10258 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');
10259 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');
10260 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');
10261 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');
10262 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.');
10263 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.');
10264 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10265 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10266 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10267 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');
10268 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');
10269 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');
10270 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10271 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10272 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');
10273 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');
10274 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');
10275 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');
10276 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');
10277 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');
10278 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');
10279 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10280 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10281 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10282 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10283 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10284 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.');
10285 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.');
10286 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1/2 in.');
10287 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1 in.');
10288 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'2 in.');
10289 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.');
10290 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10291 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10292 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10293 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10294 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10295 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');
10296 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');
10297 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10298 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10299 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10300
10301 -- Fixed Field position data -- 0-based!
10302 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10303 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10304 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10305 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10306 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10307 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10308 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10309 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10310 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10311 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10312 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10313 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10314 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10315 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10316 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10317 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10318 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10319 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10320 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10321 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10322 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10323 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10324 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10325 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10326 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10327 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10328 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10329 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10330 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10331 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10332 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10333 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10334 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10335 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10336 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10337 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10338 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10339 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10340 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10341 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10342 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10343 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10344 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10345 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10346 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10347 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10348 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10349 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10350 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10351 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10352 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10353 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10354 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10355 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10356 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10357 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10358 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10359 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10360 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10361 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10362 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10363 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10364 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10365 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10366 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10367 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10368 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10369 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10370 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10371 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10372 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10373 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10374 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10375 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10376 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10377 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10378 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10379 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10380 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10381 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10382 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10383 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10384 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10385 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10386 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10387 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10388 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10389 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10390 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10391 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10392 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10393 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10394 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10395 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10396 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10397 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10398 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10399 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10400 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10401 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10402 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10403 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10404 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10405 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10406 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10407 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10408 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10409 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10410 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10411 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10412 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10413 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10414 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10415 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10416 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10417 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10418 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10419 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10420 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10421 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10422 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10423 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10424 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10425 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10426 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10427 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10428 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10429 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10430 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10431 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10432 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10433 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10434 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10435 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10436 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');
10437 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');
10438 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10439 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10440 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10441 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10442 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10443 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10444 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10445 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10446 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10447 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10448
10449 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10450 DECLARE
10451         ldr         RECORD;
10452         tval        TEXT;
10453         tval_rec    RECORD;
10454         bval        TEXT;
10455         bval_rec    RECORD;
10456     retval      config.marc21_rec_type_map%ROWTYPE;
10457 BEGIN
10458     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10459
10460     IF ldr.id IS NULL THEN
10461         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10462         RETURN retval;
10463     END IF;
10464
10465     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10466     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10467
10468
10469     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10470     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10471
10472     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10473
10474     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10475
10476
10477     IF retval.code IS NULL THEN
10478         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10479     END IF;
10480
10481     RETURN retval;
10482 END;
10483 $func$ LANGUAGE PLPGSQL;
10484
10485 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10486 DECLARE
10487     rtype       TEXT;
10488     ff_pos      RECORD;
10489     tag_data    RECORD;
10490     val         TEXT;
10491 BEGIN
10492     rtype := (biblio.marc21_record_type( rid )).code;
10493     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10494         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10495             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10496             RETURN val;
10497         END LOOP;
10498         val := REPEAT( ff_pos.default_val, ff_pos.length );
10499         RETURN val;
10500     END LOOP;
10501
10502     RETURN NULL;
10503 END;
10504 $func$ LANGUAGE PLPGSQL;
10505
10506 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10507 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10508 DECLARE
10509     rowid   INT := 0;
10510     _007    RECORD;
10511     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10512     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10513     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10514     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10515 BEGIN
10516
10517     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10518
10519     IF _007.id IS NOT NULL THEN
10520         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10521
10522         IF ptype.ptype_key IS NOT NULL THEN
10523             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10524                 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 );
10525
10526                 IF pval.id IS NOT NULL THEN
10527                     rowid := rowid + 1;
10528                     retval.id := rowid;
10529                     retval.record := rid;
10530                     retval.ptype := ptype.ptype_key;
10531                     retval.subfield := psf.id;
10532                     retval.value := pval.id;
10533                     RETURN NEXT retval;
10534                 END IF;
10535
10536             END LOOP;
10537         END IF;
10538     END IF;
10539
10540     RETURN;
10541 END;
10542 $func$ LANGUAGE PLPGSQL;
10543
10544 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10545 DROP VIEW IF EXISTS money.open_usr_summary;
10546 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10547
10548 -- The view should supply defaults for numeric (amount) columns
10549 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10550     SELECT  xact.id,
10551         xact.usr,
10552         xact.xact_start,
10553         xact.xact_finish,
10554         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10555         credit.payment_ts AS last_payment_ts,
10556         credit.note AS last_payment_note,
10557         credit.payment_type AS last_payment_type,
10558         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10559         debit.billing_ts AS last_billing_ts,
10560         debit.note AS last_billing_note,
10561         debit.billing_type AS last_billing_type,
10562         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10563         p.relname AS xact_type
10564       FROM  money.billable_xact xact
10565         JOIN pg_class p ON xact.tableoid = p.oid
10566         LEFT JOIN (
10567             SELECT  billing.xact,
10568                 sum(billing.amount) AS amount,
10569                 max(billing.billing_ts) AS billing_ts,
10570                 last(billing.note) AS note,
10571                 last(billing.billing_type) AS billing_type
10572               FROM  money.billing
10573               WHERE billing.voided IS FALSE
10574               GROUP BY billing.xact
10575             ) debit ON xact.id = debit.xact
10576         LEFT JOIN (
10577             SELECT  payment_view.xact,
10578                 sum(payment_view.amount) AS amount,
10579                 max(payment_view.payment_ts) AS payment_ts,
10580                 last(payment_view.note) AS note,
10581                 last(payment_view.payment_type) AS payment_type
10582               FROM  money.payment_view
10583               WHERE payment_view.voided IS FALSE
10584               GROUP BY payment_view.xact
10585             ) credit ON xact.id = credit.xact
10586       ORDER BY debit.billing_ts, credit.payment_ts;
10587
10588 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10589     SELECT * FROM money.billable_xact_summary_location_view
10590     WHERE xact_finish IS NULL;
10591
10592 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10593     SELECT 
10594         usr,
10595         SUM(total_paid) AS total_paid,
10596         SUM(total_owed) AS total_owed,
10597         SUM(balance_owed) AS balance_owed
10598     FROM  money.materialized_billable_xact_summary
10599     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10600     GROUP BY usr;
10601
10602 CREATE OR REPLACE VIEW money.usr_summary AS
10603     SELECT 
10604         usr, 
10605         sum(total_paid) AS total_paid, 
10606         sum(total_owed) AS total_owed, 
10607         sum(balance_owed) AS balance_owed
10608     FROM money.materialized_billable_xact_summary
10609     GROUP BY usr;
10610
10611 CREATE OR REPLACE VIEW money.open_usr_summary AS
10612     SELECT 
10613         usr, 
10614         sum(total_paid) AS total_paid, 
10615         sum(total_owed) AS total_owed, 
10616         sum(balance_owed) AS balance_owed
10617     FROM money.materialized_billable_xact_summary
10618     WHERE xact_finish IS NULL
10619     GROUP BY usr;
10620
10621 -- 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;
10622
10623 CREATE TABLE config.biblio_fingerprint (
10624         id                      SERIAL  PRIMARY KEY,
10625         name            TEXT    NOT NULL, 
10626         xpath           TEXT    NOT NULL,
10627     first_word  BOOL    NOT NULL DEFAULT FALSE,
10628         format          TEXT    NOT NULL DEFAULT 'marcxml'
10629 );
10630
10631 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10632     VALUES (
10633         'Title',
10634         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10635             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10636             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10637             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10638             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10639         'marcxml'
10640     );
10641
10642 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10643     VALUES (
10644         'Author',
10645         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10646             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10647             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10648             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10649             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10650         'marcxml',
10651         TRUE
10652     );
10653
10654 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10655 DECLARE
10656     qual        INT;
10657     ldr         TEXT;
10658     tval        TEXT;
10659     tval_rec    RECORD;
10660     bval        TEXT;
10661     bval_rec    RECORD;
10662     type_map    RECORD;
10663     ff_pos      RECORD;
10664     ff_tag_data TEXT;
10665 BEGIN
10666
10667     IF marc IS NULL OR marc = '' THEN
10668         RETURN NULL;
10669     END IF;
10670
10671     -- First, the count of tags
10672     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10673
10674     -- now go through a bunch of pain to get the record type
10675     IF best_type IS NOT NULL THEN
10676         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10677
10678         IF ldr IS NOT NULL THEN
10679             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10680             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10681
10682
10683             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10684             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10685
10686             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10687
10688             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10689
10690             IF type_map.code IS NOT NULL THEN
10691                 IF best_type = type_map.code THEN
10692                     qual := qual + qual / 2;
10693                 END IF;
10694
10695                 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
10696                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10697                     IF ff_tag_data = best_lang THEN
10698                             qual := qual + 100;
10699                     END IF;
10700                 END LOOP;
10701             END IF;
10702         END IF;
10703     END IF;
10704
10705     -- Now look for some quality metrics
10706     -- DCL record?
10707     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10708         qual := qual + 10;
10709     END IF;
10710
10711     -- From OCLC?
10712     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10713         qual := qual + 10;
10714     END IF;
10715
10716     RETURN qual;
10717
10718 END;
10719 $func$ LANGUAGE PLPGSQL;
10720
10721 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10722 DECLARE
10723     idx     config.biblio_fingerprint%ROWTYPE;
10724     xfrm        config.xml_transform%ROWTYPE;
10725     prev_xfrm   TEXT;
10726     transformed_xml TEXT;
10727     xml_node    TEXT;
10728     xml_node_list   TEXT[];
10729     raw_text    TEXT;
10730     output_text TEXT := '';
10731 BEGIN
10732
10733     IF marc IS NULL OR marc = '' THEN
10734         RETURN NULL;
10735     END IF;
10736
10737     -- Loop over the indexing entries
10738     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10739
10740         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10741
10742         -- See if we can skip the XSLT ... it's expensive
10743         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10744             -- Can't skip the transform
10745             IF xfrm.xslt <> '---' THEN
10746                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10747             ELSE
10748                 transformed_xml := marc;
10749             END IF;
10750
10751             prev_xfrm := xfrm.name;
10752         END IF;
10753
10754         raw_text := COALESCE(
10755             naco_normalize(
10756                 ARRAY_TO_STRING(
10757                     oils_xpath(
10758                         '//text()',
10759                         (oils_xpath(
10760                             idx.xpath,
10761                             transformed_xml,
10762                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10763                         ))[1]
10764                     ),
10765                     ''
10766                 )
10767             ),
10768             ''
10769         );
10770
10771         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10772         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10773
10774         IF idx.first_word IS TRUE THEN
10775             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10776         END IF;
10777
10778         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10779
10780     END LOOP;
10781
10782     RETURN output_text;
10783
10784 END;
10785 $func$ LANGUAGE PLPGSQL;
10786
10787 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10788 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10789 BEGIN
10790
10791     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10792
10793     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10794         RETURN NEW;
10795     END IF;
10796
10797     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10798     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10799
10800     RETURN NEW;
10801
10802 END;
10803 $func$ LANGUAGE PLPGSQL;
10804
10805 CREATE TABLE config.internal_flag (
10806     name    TEXT    PRIMARY KEY,
10807     value   TEXT,
10808     enabled BOOL    NOT NULL DEFAULT FALSE
10809 );
10810 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10811 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10812 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10813 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10814 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10815 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10816 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10817 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10818 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10819 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10820
10821 CREATE TABLE authority.bib_linking (
10822     id          BIGSERIAL   PRIMARY KEY,
10823     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10824     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10825 );
10826 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10827 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10828
10829 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10830     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10831 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10832
10833 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10834     DELETE FROM authority.bib_linking WHERE bib = $1;
10835     INSERT INTO authority.bib_linking (bib, authority)
10836         SELECT  y.bib,
10837                 y.authority
10838           FROM (    SELECT  DISTINCT $1 AS bib,
10839                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10840                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10841                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10842                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10843     SELECT $1;
10844 $func$ LANGUAGE SQL;
10845
10846 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10847 BEGIN
10848     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10849     IF NOT FOUND THEN
10850         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10851     END IF;
10852     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)
10853         SELECT  bib_id,
10854                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10855                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10856                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10857                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10858                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10859                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10860                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10861                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10862                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10863                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10864                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10865                 (   SELECT  v.value
10866                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10867                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10868                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10869                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10870                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
10871                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
10872
10873     RETURN;
10874 END;
10875 $func$ LANGUAGE PLPGSQL;
10876
10877 CREATE TABLE config.metabib_class (
10878     name    TEXT    PRIMARY KEY,
10879     label   TEXT    NOT NULL UNIQUE
10880 );
10881
10882 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10883 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10884 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10885 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10886 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10887
10888 CREATE TABLE metabib.facet_entry (
10889         id              BIGSERIAL       PRIMARY KEY,
10890         source          BIGINT          NOT NULL,
10891         field           INT             NOT NULL,
10892         value           TEXT            NOT NULL
10893 );
10894
10895 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10896 DECLARE
10897     fclass          RECORD;
10898     ind_data        metabib.field_entry_template%ROWTYPE;
10899 BEGIN
10900     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10901     IF NOT FOUND THEN
10902         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10903             -- RAISE NOTICE 'Emptying out %', fclass.name;
10904             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10905         END LOOP;
10906         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10907     END IF;
10908
10909     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10910         IF ind_data.field < 0 THEN
10911             ind_data.field = -1 * ind_data.field;
10912             INSERT INTO metabib.facet_entry (field, source, value)
10913                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10914         ELSE
10915             EXECUTE $$
10916                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10917                     VALUES ($$ ||
10918                         quote_literal(ind_data.field) || $$, $$ ||
10919                         quote_literal(ind_data.source) || $$, $$ ||
10920                         quote_literal(ind_data.value) ||
10921                     $$);$$;
10922         END IF;
10923
10924     END LOOP;
10925
10926     RETURN;
10927 END;
10928 $func$ LANGUAGE PLPGSQL;
10929
10930 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
10931 DECLARE
10932     uris            TEXT[];
10933     uri_xml         TEXT;
10934     uri_label       TEXT;
10935     uri_href        TEXT;
10936     uri_use         TEXT;
10937     uri_owner       TEXT;
10938     uri_owner_id    INT;
10939     uri_id          INT;
10940     uri_cn_id       INT;
10941     uri_map_id      INT;
10942 BEGIN
10943
10944     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
10945     IF ARRAY_UPPER(uris,1) > 0 THEN
10946         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
10947             -- First we pull info out of the 856
10948             uri_xml     := uris[i];
10949
10950             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
10951             CONTINUE WHEN uri_href IS NULL;
10952
10953             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10954             CONTINUE WHEN uri_label IS NULL;
10955
10956             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
10957             CONTINUE WHEN uri_owner IS NULL;
10958
10959             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10960
10961             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
10962
10963             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
10964             CONTINUE WHEN NOT FOUND;
10965
10966             -- now we look for a matching uri
10967             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10968             IF NOT FOUND THEN -- create one
10969                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
10970                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10971             END IF;
10972
10973             -- we need a call number to link through
10974             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;
10975             IF NOT FOUND THEN
10976                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
10977                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
10978                 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;
10979             END IF;
10980
10981             -- now, link them if they're not already
10982             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
10983             IF NOT FOUND THEN
10984                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
10985             END IF;
10986
10987         END LOOP;
10988     END IF;
10989
10990     RETURN;
10991 END;
10992 $func$ LANGUAGE PLPGSQL;
10993
10994 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
10995 DECLARE
10996     source_count    INT;
10997     old_mr          BIGINT;
10998     tmp_mr          metabib.metarecord%ROWTYPE;
10999     deleted_mrs     BIGINT[];
11000 BEGIN
11001
11002     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
11003
11004     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
11005
11006         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
11007             old_mr := tmp_mr.id;
11008         ELSE
11009             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
11010             IF source_count = 0 THEN -- No other records
11011                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
11012                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
11013             END IF;
11014         END IF;
11015
11016     END LOOP;
11017
11018     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
11019         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
11020         IF old_mr IS NULL THEN -- nope, create one and grab its id
11021             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
11022             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
11023         ELSE -- indeed there is. update it with a null cache and recalcualated master record
11024             UPDATE  metabib.metarecord
11025               SET   mods = NULL,
11026                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11027               WHERE id = old_mr;
11028         END IF;
11029     ELSE -- there was one we already attached to, update its mods cache and master_record
11030         UPDATE  metabib.metarecord
11031           SET   mods = NULL,
11032                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11033           WHERE id = old_mr;
11034     END IF;
11035
11036     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
11037
11038     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
11039         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
11040     END IF;
11041
11042     RETURN old_mr;
11043
11044 END;
11045 $func$ LANGUAGE PLPGSQL;
11046
11047 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
11048 BEGIN
11049     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
11050     IF NOT FOUND THEN
11051         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
11052     END IF;
11053     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
11054         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
11055
11056     RETURN;
11057 END;
11058 $func$ LANGUAGE PLPGSQL;
11059
11060 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
11061 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
11062 BEGIN
11063
11064     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
11065         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
11066         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
11067         RETURN NEW; -- and we're done
11068     END IF;
11069
11070     IF TG_OP = 'UPDATE' THEN -- re-ingest?
11071         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
11072
11073         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
11074             RETURN NEW;
11075         END IF;
11076     END IF;
11077
11078     -- Record authority linking
11079     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
11080     IF NOT FOUND THEN
11081         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11082     END IF;
11083
11084     -- Flatten and insert the mfr data
11085     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11086     IF NOT FOUND THEN
11087         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11088         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11089         IF NOT FOUND THEN
11090             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
11091         END IF;
11092     END IF;
11093
11094     -- Gather and insert the field entry data
11095     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11096
11097     -- Located URI magic
11098     IF TG_OP = 'INSERT' THEN
11099         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11100         IF NOT FOUND THEN
11101             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11102         END IF;
11103     ELSE
11104         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11105         IF NOT FOUND THEN
11106             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11107         END IF;
11108     END IF;
11109
11110     -- (re)map metarecord-bib linking
11111     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11112         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11113         IF NOT FOUND THEN
11114             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11115         END IF;
11116     ELSE -- we're doing an update, and we're not deleted, remap
11117         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11118         IF NOT FOUND THEN
11119             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11120         END IF;
11121     END IF;
11122
11123     RETURN NEW;
11124 END;
11125 $func$ LANGUAGE PLPGSQL;
11126
11127 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
11128 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 ();
11129
11130 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
11131
11132 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
11133 DECLARE
11134     xpath_list  TEXT[];
11135     select_list TEXT[];
11136     where_list  TEXT[];
11137     q           TEXT;
11138     out_record  RECORD;
11139     empty_test  RECORD;
11140 BEGIN
11141     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
11142  
11143     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
11144  
11145     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
11146         IF xpath_list[i] = 'null()' THEN
11147             select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
11148         ELSE
11149             select_list := ARRAY_APPEND(
11150                 select_list,
11151                 $sel$
11152                 EXPLODE_ARRAY(
11153                     COALESCE(
11154                         NULLIF(
11155                             oils_xpath(
11156                                 $sel$ ||
11157                                     quote_literal(
11158                                         CASE
11159                                             WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
11160                                             ELSE xpath_list[i] || '//text()'
11161                                         END
11162                                     ) ||
11163                                 $sel$,
11164                                 $sel$ || document_field || $sel$
11165                             ),
11166                            '{}'::TEXT[]
11167                         ),
11168                         '{NULL}'::TEXT[]
11169                     )
11170                 ) AS c_$sel$ || i
11171             );
11172             where_list := ARRAY_APPEND(
11173                 where_list,
11174                 'c_' || i || ' IS NOT NULL'
11175             );
11176         END IF;
11177     END LOOP;
11178  
11179     q := $q$
11180 SELECT * FROM (
11181     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11182 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
11183     -- RAISE NOTICE 'query: %', q;
11184  
11185     FOR out_record IN EXECUTE q LOOP
11186         RETURN NEXT out_record;
11187     END LOOP;
11188  
11189     RETURN;
11190 END;
11191 $func$ LANGUAGE PLPGSQL IMMUTABLE;
11192
11193 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11194 DECLARE
11195
11196     owning_lib      TEXT;
11197     circ_lib        TEXT;
11198     call_number     TEXT;
11199     copy_number     TEXT;
11200     status          TEXT;
11201     location        TEXT;
11202     circulate       TEXT;
11203     deposit         TEXT;
11204     deposit_amount  TEXT;
11205     ref             TEXT;
11206     holdable        TEXT;
11207     price           TEXT;
11208     barcode         TEXT;
11209     circ_modifier   TEXT;
11210     circ_as_type    TEXT;
11211     alert_message   TEXT;
11212     opac_visible    TEXT;
11213     pub_note        TEXT;
11214     priv_note       TEXT;
11215
11216     attr_def        RECORD;
11217     tmp_attr_set    RECORD;
11218     attr_set        vandelay.import_item%ROWTYPE;
11219
11220     xpath           TEXT;
11221
11222 BEGIN
11223
11224     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11225
11226     IF FOUND THEN
11227
11228         attr_set.definition := attr_def.id; 
11229     
11230         -- Build the combined XPath
11231     
11232         owning_lib :=
11233             CASE
11234                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11235                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11236                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11237             END;
11238     
11239         circ_lib :=
11240             CASE
11241                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11242                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11243                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11244             END;
11245     
11246         call_number :=
11247             CASE
11248                 WHEN attr_def.call_number IS NULL THEN 'null()'
11249                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11250                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11251             END;
11252     
11253         copy_number :=
11254             CASE
11255                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11256                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11257                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11258             END;
11259     
11260         status :=
11261             CASE
11262                 WHEN attr_def.status IS NULL THEN 'null()'
11263                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11264                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11265             END;
11266     
11267         location :=
11268             CASE
11269                 WHEN attr_def.location IS NULL THEN 'null()'
11270                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11271                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11272             END;
11273     
11274         circulate :=
11275             CASE
11276                 WHEN attr_def.circulate IS NULL THEN 'null()'
11277                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11278                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11279             END;
11280     
11281         deposit :=
11282             CASE
11283                 WHEN attr_def.deposit IS NULL THEN 'null()'
11284                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11285                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11286             END;
11287     
11288         deposit_amount :=
11289             CASE
11290                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11291                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11292                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11293             END;
11294     
11295         ref :=
11296             CASE
11297                 WHEN attr_def.ref IS NULL THEN 'null()'
11298                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11299                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11300             END;
11301     
11302         holdable :=
11303             CASE
11304                 WHEN attr_def.holdable IS NULL THEN 'null()'
11305                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11306                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11307             END;
11308     
11309         price :=
11310             CASE
11311                 WHEN attr_def.price IS NULL THEN 'null()'
11312                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11313                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11314             END;
11315     
11316         barcode :=
11317             CASE
11318                 WHEN attr_def.barcode IS NULL THEN 'null()'
11319                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11320                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11321             END;
11322     
11323         circ_modifier :=
11324             CASE
11325                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11326                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11327                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11328             END;
11329     
11330         circ_as_type :=
11331             CASE
11332                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11333                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11334                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11335             END;
11336     
11337         alert_message :=
11338             CASE
11339                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11340                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11341                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11342             END;
11343     
11344         opac_visible :=
11345             CASE
11346                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11347                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11348                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11349             END;
11350
11351         pub_note :=
11352             CASE
11353                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11354                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11355                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11356             END;
11357         priv_note :=
11358             CASE
11359                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11360                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11361                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11362             END;
11363     
11364     
11365         xpath := 
11366             owning_lib      || '|' || 
11367             circ_lib        || '|' || 
11368             call_number     || '|' || 
11369             copy_number     || '|' || 
11370             status          || '|' || 
11371             location        || '|' || 
11372             circulate       || '|' || 
11373             deposit         || '|' || 
11374             deposit_amount  || '|' || 
11375             ref             || '|' || 
11376             holdable        || '|' || 
11377             price           || '|' || 
11378             barcode         || '|' || 
11379             circ_modifier   || '|' || 
11380             circ_as_type    || '|' || 
11381             alert_message   || '|' || 
11382             pub_note        || '|' || 
11383             priv_note       || '|' || 
11384             opac_visible;
11385
11386         -- RAISE NOTICE 'XPath: %', xpath;
11387         
11388         FOR tmp_attr_set IN
11389                 SELECT  *
11390                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11391                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11392                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11393                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11394         LOOP
11395     
11396             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11397             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11398
11399             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11400             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11401     
11402             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11403             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11404             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11405     
11406             SELECT  id INTO attr_set.location
11407               FROM  asset.copy_location
11408               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11409                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11410     
11411             attr_set.circulate      :=
11412                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11413                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11414
11415             attr_set.deposit        :=
11416                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11417                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11418
11419             attr_set.holdable       :=
11420                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11421                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11422
11423             attr_set.opac_visible   :=
11424                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11425                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11426
11427             attr_set.ref            :=
11428                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11429                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11430     
11431             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11432             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11433             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11434     
11435             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11436             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11437             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11438             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11439             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11440             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11441             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11442             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11443     
11444             RETURN NEXT attr_set;
11445     
11446         END LOOP;
11447     
11448     END IF;
11449
11450     RETURN;
11451
11452 END;
11453 $$ LANGUAGE PLPGSQL;
11454
11455 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11456 DECLARE
11457     attr_def    BIGINT;
11458     item_data   vandelay.import_item%ROWTYPE;
11459 BEGIN
11460
11461     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11462
11463     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11464         INSERT INTO vandelay.import_item (
11465             record,
11466             definition,
11467             owning_lib,
11468             circ_lib,
11469             call_number,
11470             copy_number,
11471             status,
11472             location,
11473             circulate,
11474             deposit,
11475             deposit_amount,
11476             ref,
11477             holdable,
11478             price,
11479             barcode,
11480             circ_modifier,
11481             circ_as_type,
11482             alert_message,
11483             pub_note,
11484             priv_note,
11485             opac_visible
11486         ) VALUES (
11487             NEW.id,
11488             item_data.definition,
11489             item_data.owning_lib,
11490             item_data.circ_lib,
11491             item_data.call_number,
11492             item_data.copy_number,
11493             item_data.status,
11494             item_data.location,
11495             item_data.circulate,
11496             item_data.deposit,
11497             item_data.deposit_amount,
11498             item_data.ref,
11499             item_data.holdable,
11500             item_data.price,
11501             item_data.barcode,
11502             item_data.circ_modifier,
11503             item_data.circ_as_type,
11504             item_data.alert_message,
11505             item_data.pub_note,
11506             item_data.priv_note,
11507             item_data.opac_visible
11508         );
11509     END LOOP;
11510
11511     RETURN NULL;
11512 END;
11513 $func$ LANGUAGE PLPGSQL;
11514
11515 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11516 BEGIN
11517     EXECUTE $$
11518         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11519     $$;
11520         RETURN TRUE;
11521 END;
11522 $creator$ LANGUAGE 'plpgsql';
11523
11524 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11525 BEGIN
11526     EXECUTE $$
11527         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11528             audit_id    BIGINT                          PRIMARY KEY,
11529             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11530             audit_action        TEXT                            NOT NULL,
11531             LIKE $$ || sch || $$.$$ || tbl || $$
11532         );
11533     $$;
11534         RETURN TRUE;
11535 END;
11536 $creator$ LANGUAGE 'plpgsql';
11537
11538 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11539 BEGIN
11540     EXECUTE $$
11541         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11542         RETURNS TRIGGER AS $func$
11543         BEGIN
11544             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11545                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11546                     now(),
11547                     SUBSTR(TG_OP,1,1),
11548                     OLD.*;
11549             RETURN NULL;
11550         END;
11551         $func$ LANGUAGE 'plpgsql';
11552     $$;
11553         RETURN TRUE;
11554 END;
11555 $creator$ LANGUAGE 'plpgsql';
11556
11557 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11558 BEGIN
11559     EXECUTE $$
11560         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11561             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11562             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11563     $$;
11564         RETURN TRUE;
11565 END;
11566 $creator$ LANGUAGE 'plpgsql';
11567
11568 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11569 BEGIN
11570     EXECUTE $$
11571         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11572             SELECT      -1, now() as audit_time, '-' as audit_action, *
11573               FROM      $$ || sch || $$.$$ || tbl || $$
11574                 UNION ALL
11575             SELECT      *
11576               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11577     $$;
11578         RETURN TRUE;
11579 END;
11580 $creator$ LANGUAGE 'plpgsql';
11581
11582 -- The main event
11583
11584 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11585 BEGIN
11586     PERFORM acq.create_acq_seq(sch, tbl);
11587     PERFORM acq.create_acq_history(sch, tbl);
11588     PERFORM acq.create_acq_func(sch, tbl);
11589     PERFORM acq.create_acq_update_trigger(sch, tbl);
11590     PERFORM acq.create_acq_lifecycle(sch, tbl);
11591     RETURN TRUE;
11592 END;
11593 $creator$ LANGUAGE 'plpgsql';
11594
11595 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11596
11597 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11598     SELECT  fund.id AS fund,
11599             fund_debit.encumbrance AS encumbrance,
11600             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11601       FROM acq.fund AS fund
11602                         LEFT JOIN acq.fund_debit AS fund_debit
11603                                 ON ( fund.id = fund_debit.fund )
11604       GROUP BY 1,2;
11605
11606 CREATE TABLE acq.debit_attribution (
11607         id                     INT         NOT NULL PRIMARY KEY,
11608         fund_debit             INT         NOT NULL
11609                                            REFERENCES acq.fund_debit
11610                                            DEFERRABLE INITIALLY DEFERRED,
11611     debit_amount           NUMERIC     NOT NULL,
11612         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11613                                            DEFERRABLE INITIALLY DEFERRED,
11614     credit_amount          NUMERIC
11615 );
11616
11617 CREATE INDEX acq_attribution_debit_idx
11618         ON acq.debit_attribution( fund_debit );
11619
11620 CREATE INDEX acq_attribution_credit_idx
11621         ON acq.debit_attribution( funding_source_credit );
11622
11623 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11624 /*
11625 Function to attribute expenditures and encumbrances to funding source credits,
11626 and thereby to funding sources.
11627
11628 Read the debits in chonological order, attributing each one to one or
11629 more funding source credits.  Constraints:
11630
11631 1. Don't attribute more to a credit than the amount of the credit.
11632
11633 2. For a given fund, don't attribute more to a funding source than the
11634 source has allocated to that fund.
11635
11636 3. Attribute debits to credits with deadlines before attributing them to
11637 credits without deadlines.  Otherwise attribute to the earliest credits
11638 first, based on the deadline date when present, or on the effective date
11639 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11640 This ordering is defined by an ORDER BY clause on the view
11641 acq.ordered_funding_source_credit.
11642
11643 Start by truncating the table acq.debit_attribution.  Then insert a row
11644 into that table for each attribution.  If a debit cannot be fully
11645 attributed, insert a row for the unattributable balance, with the 
11646 funding_source_credit and credit_amount columns NULL.
11647 */
11648 DECLARE
11649         curr_fund_source_bal RECORD;
11650         seqno                INT;     -- sequence num for credits applicable to a fund
11651         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11652         fc                   RECORD;  -- used for loading t_fund_credit table
11653         sc                   RECORD;  -- used for loading t_fund_credit table
11654         --
11655         -- Used exclusively in the main loop:
11656         --
11657         deb                 RECORD;   -- current row from acq.fund_debit table
11658         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11659         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11660         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11661         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11662         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11663         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11664         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11665         attrib_count        INT;      -- populates id of acq.debit_attribution
11666 BEGIN
11667         --
11668         -- Load a temporary table.  For each combination of fund and funding source,
11669         -- load an entry with the total amount allocated to that fund by that source.
11670         -- This sum may reflect transfers as well as original allocations.  We will
11671         -- reduce this balance whenever we attribute debits to it.
11672         --
11673         CREATE TEMP TABLE t_fund_source_bal
11674         ON COMMIT DROP AS
11675                 SELECT
11676                         fund AS fund,
11677                         funding_source AS source,
11678                         sum( amount ) AS balance
11679                 FROM
11680                         acq.fund_allocation
11681                 GROUP BY
11682                         fund,
11683                         funding_source
11684                 HAVING
11685                         sum( amount ) > 0;
11686         --
11687         CREATE INDEX t_fund_source_bal_idx
11688                 ON t_fund_source_bal( fund, source );
11689         -------------------------------------------------------------------------------
11690         --
11691         -- Load another temporary table.  For each fund, load zero or more
11692         -- funding source credits from which that fund can get money.
11693         --
11694         CREATE TEMP TABLE t_fund_credit (
11695                 fund        INT,
11696                 seq         INT,
11697                 credit      INT
11698         ) ON COMMIT DROP;
11699         --
11700         FOR fc IN
11701                 SELECT DISTINCT fund
11702                 FROM acq.fund_allocation
11703                 ORDER BY fund
11704         LOOP                  -- Loop over the funds
11705                 seqno := 1;
11706                 FOR sc IN
11707                         SELECT
11708                                 ofsc.id
11709                         FROM
11710                                 acq.ordered_funding_source_credit AS ofsc
11711                         WHERE
11712                                 ofsc.funding_source IN
11713                                 (
11714                                         SELECT funding_source
11715                                         FROM acq.fund_allocation
11716                                         WHERE fund = fc.fund
11717                                 )
11718                 ORDER BY
11719                     ofsc.sort_priority,
11720                     ofsc.sort_date,
11721                     ofsc.id
11722                 LOOP                        -- Add each credit to the list
11723                         INSERT INTO t_fund_credit (
11724                                 fund,
11725                                 seq,
11726                                 credit
11727                         ) VALUES (
11728                                 fc.fund,
11729                                 seqno,
11730                                 sc.id
11731                         );
11732                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11733                         seqno := seqno + 1;
11734                 END LOOP;     -- Loop over credits for a given fund
11735         END LOOP;         -- Loop over funds
11736         --
11737         CREATE INDEX t_fund_credit_idx
11738                 ON t_fund_credit( fund, seq );
11739         -------------------------------------------------------------------------------
11740         --
11741         -- Load yet another temporary table.  This one is a list of funding source
11742         -- credits, with their balances.  We shall reduce those balances as we
11743         -- attribute debits to them.
11744         --
11745         CREATE TEMP TABLE t_credit
11746         ON COMMIT DROP AS
11747         SELECT
11748             fsc.id AS credit,
11749             fsc.funding_source AS source,
11750             fsc.amount AS balance,
11751             fs.currency_type AS currency_type
11752         FROM
11753             acq.funding_source_credit AS fsc,
11754             acq.funding_source fs
11755         WHERE
11756             fsc.funding_source = fs.id
11757                         AND fsc.amount > 0;
11758         --
11759         CREATE INDEX t_credit_idx
11760                 ON t_credit( credit );
11761         --
11762         -------------------------------------------------------------------------------
11763         --
11764         -- Now that we have loaded the lookup tables: loop through the debits,
11765         -- attributing each one to one or more funding source credits.
11766         -- 
11767         truncate table acq.debit_attribution;
11768         --
11769         attrib_count := 0;
11770         FOR deb in
11771                 SELECT
11772                         fd.id,
11773                         fd.fund,
11774                         fd.amount,
11775                         f.currency_type,
11776                         fd.encumbrance
11777                 FROM
11778                         acq.fund_debit fd,
11779                         acq.fund f
11780                 WHERE
11781                         fd.fund = f.id
11782                 ORDER BY
11783                         fd.id
11784         LOOP
11785                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11786                 --
11787                 debit_balance := deb.amount;
11788                 --
11789                 -- Loop over the funding source credits that are eligible
11790                 -- to pay for this debit
11791                 --
11792                 FOR fund_credit IN
11793                         SELECT
11794                                 credit
11795                         FROM
11796                                 t_fund_credit
11797                         WHERE
11798                                 fund = deb.fund
11799                         ORDER BY
11800                                 seq
11801                 LOOP
11802                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11803                         --
11804                         -- Look up the balance for this credit.  If it's zero, then
11805                         -- it's not useful, so treat it as if you didn't find it.
11806                         -- (Actually there shouldn't be any zero balances in the table,
11807                         -- but we check just to make sure.)
11808                         --
11809                         SELECT *
11810                         INTO curr_credit_bal
11811                         FROM t_credit
11812                         WHERE
11813                                 credit = fund_credit.credit
11814                                 AND balance > 0;
11815                         --
11816                         IF curr_credit_bal IS NULL THEN
11817                                 --
11818                                 -- This credit is exhausted; try the next one.
11819                                 --
11820                                 CONTINUE;
11821                         END IF;
11822                         --
11823                         --
11824                         -- At this point we have an applicable credit with some money left.
11825                         -- Now see if the relevant funding_source has any money left.
11826                         --
11827                         -- Look up the balance of the allocation for this combination of
11828                         -- fund and source.  If you find such an entry, but it has a zero
11829                         -- balance, then it's not useful, so treat it as unfound.
11830                         -- (Actually there shouldn't be any zero balances in the table,
11831                         -- but we check just to make sure.)
11832                         --
11833                         SELECT *
11834                         INTO curr_fund_source_bal
11835                         FROM t_fund_source_bal
11836                         WHERE
11837                                 fund = deb.fund
11838                                 AND source = curr_credit_bal.source
11839                                 AND balance > 0;
11840                         --
11841                         IF curr_fund_source_bal IS NULL THEN
11842                                 --
11843                                 -- This fund/source doesn't exist or is already exhausted,
11844                                 -- so we can't use this credit.  Go on to the next one.
11845                                 --
11846                                 CONTINUE;
11847                         END IF;
11848                         --
11849                         -- Convert the available balances to the currency of the fund
11850                         --
11851                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11852                                 curr_credit_bal.currency_type, deb.currency_type );
11853                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11854                                 curr_credit_bal.currency_type, deb.currency_type );
11855                         --
11856                         -- Determine how much we can attribute to this credit: the minimum
11857                         -- of the debit amount, the fund/source balance, and the
11858                         -- credit balance
11859                         --
11860                         --RAISE NOTICE '   deb bal %', debit_balance;
11861                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11862                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11863                         --
11864                         conv_attr_amount := NULL;
11865                         attr_amount := debit_balance;
11866                         --
11867                         IF attr_amount > conv_alloc_balance THEN
11868                                 attr_amount := conv_alloc_balance;
11869                                 conv_attr_amount := curr_fund_source_bal.balance;
11870                         END IF;
11871                         IF attr_amount > conv_cred_balance THEN
11872                                 attr_amount := conv_cred_balance;
11873                                 conv_attr_amount := curr_credit_bal.balance;
11874                         END IF;
11875                         --
11876                         -- If we're attributing all of one of the balances, then that's how
11877                         -- much we will deduct from the balances, and we already captured
11878                         -- that amount above.  Otherwise we must convert the amount of the
11879                         -- attribution from the currency of the fund back to the currency of
11880                         -- the funding source.
11881                         --
11882                         IF conv_attr_amount IS NULL THEN
11883                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11884                                         deb.currency_type, curr_credit_bal.currency_type );
11885                         END IF;
11886                         --
11887                         -- Insert a row to record the attribution
11888                         --
11889                         attrib_count := attrib_count + 1;
11890                         INSERT INTO acq.debit_attribution (
11891                                 id,
11892                                 fund_debit,
11893                                 debit_amount,
11894                                 funding_source_credit,
11895                                 credit_amount
11896                         ) VALUES (
11897                                 attrib_count,
11898                                 deb.id,
11899                                 attr_amount,
11900                                 curr_credit_bal.credit,
11901                                 conv_attr_amount
11902                         );
11903                         --
11904                         -- Subtract the attributed amount from the various balances
11905                         --
11906                         debit_balance := debit_balance - attr_amount;
11907                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11908                         --
11909                         IF curr_fund_source_bal.balance <= 0 THEN
11910                                 --
11911                                 -- This allocation is exhausted.  Delete it so
11912                                 -- that we don't waste time looking at it again.
11913                                 --
11914                                 DELETE FROM t_fund_source_bal
11915                                 WHERE
11916                                         fund = curr_fund_source_bal.fund
11917                                         AND source = curr_fund_source_bal.source;
11918                         ELSE
11919                                 UPDATE t_fund_source_bal
11920                                 SET balance = balance - conv_attr_amount
11921                                 WHERE
11922                                         fund = curr_fund_source_bal.fund
11923                                         AND source = curr_fund_source_bal.source;
11924                         END IF;
11925                         --
11926                         IF curr_credit_bal.balance <= 0 THEN
11927                                 --
11928                                 -- This funding source credit is exhausted.  Delete it
11929                                 -- so that we don't waste time looking at it again.
11930                                 --
11931                                 --DELETE FROM t_credit
11932                                 --WHERE
11933                                 --      credit = curr_credit_bal.credit;
11934                                 --
11935                                 DELETE FROM t_fund_credit
11936                                 WHERE
11937                                         credit = curr_credit_bal.credit;
11938                         ELSE
11939                                 UPDATE t_credit
11940                                 SET balance = curr_credit_bal.balance
11941                                 WHERE
11942                                         credit = curr_credit_bal.credit;
11943                         END IF;
11944                         --
11945                         -- Are we done with this debit yet?
11946                         --
11947                         IF debit_balance <= 0 THEN
11948                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
11949                         END IF;
11950                 END LOOP;       -- End loop over credits
11951                 --
11952                 IF debit_balance <> 0 THEN
11953                         --
11954                         -- We weren't able to attribute this debit, or at least not
11955                         -- all of it.  Insert a row for the unattributed balance.
11956                         --
11957                         attrib_count := attrib_count + 1;
11958                         INSERT INTO acq.debit_attribution (
11959                                 id,
11960                                 fund_debit,
11961                                 debit_amount,
11962                                 funding_source_credit,
11963                                 credit_amount
11964                         ) VALUES (
11965                                 attrib_count,
11966                                 deb.id,
11967                                 debit_balance,
11968                                 NULL,
11969                                 NULL
11970                         );
11971                 END IF;
11972         END LOOP;   -- End of loop over debits
11973 END;
11974 $$ LANGUAGE 'plpgsql';
11975
11976 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
11977 DECLARE
11978     query TEXT;
11979     output TEXT;
11980 BEGIN
11981     query := $q$
11982         SELECT  regexp_replace(
11983                     oils_xpath_string(
11984                         $q$ || quote_literal($3) || $q$,
11985                         marc,
11986                         ' '
11987                     ),
11988                     $q$ || quote_literal($4) || $q$,
11989                     '',
11990                     'g')
11991           FROM  $q$ || $1 || $q$
11992           WHERE id = $q$ || $2;
11993
11994     EXECUTE query INTO output;
11995
11996     -- RAISE NOTICE 'query: %, output; %', query, output;
11997
11998     RETURN output;
11999 END;
12000 $$ LANGUAGE PLPGSQL IMMUTABLE;
12001
12002 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
12003     SELECT extract_marc_field($1,$2,$3,'');
12004 $$ LANGUAGE SQL IMMUTABLE;
12005
12006 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
12007 DECLARE
12008     moved_objects INT := 0;
12009     source_cn     asset.call_number%ROWTYPE;
12010     target_cn     asset.call_number%ROWTYPE;
12011     metarec       metabib.metarecord%ROWTYPE;
12012     hold          action.hold_request%ROWTYPE;
12013     ser_rec       serial.record_entry%ROWTYPE;
12014     uri_count     INT := 0;
12015     counter       INT := 0;
12016     uri_datafield TEXT;
12017     uri_text      TEXT := '';
12018 BEGIN
12019
12020     -- move any 856 entries on records that have at least one MARC-mapped URI entry
12021     SELECT  INTO uri_count COUNT(*)
12022       FROM  asset.uri_call_number_map m
12023             JOIN asset.call_number cn ON (m.call_number = cn.id)
12024       WHERE cn.record = source_record;
12025
12026     IF uri_count > 0 THEN
12027
12028         SELECT  COUNT(*) INTO counter
12029           FROM  oils_xpath_table(
12030                     'id',
12031                     'marc',
12032                     'biblio.record_entry',
12033                     '//*[@tag="856"]',
12034                     'id=' || source_record
12035                 ) as t(i int,c text);
12036
12037         FOR i IN 1 .. counter LOOP
12038             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
12039                         ' tag="856"' || 
12040                         ' ind1="' || FIRST(ind1) || '"'  || 
12041                         ' ind2="' || FIRST(ind2) || '">' || 
12042                         array_to_string(
12043                             array_accum(
12044                                 '<subfield code="' || subfield || '">' ||
12045                                 regexp_replace(
12046                                     regexp_replace(
12047                                         regexp_replace(data,'&','&amp;','g'),
12048                                         '>', '&gt;', 'g'
12049                                     ),
12050                                     '<', '&lt;', 'g'
12051                                 ) || '</subfield>'
12052                             ), ''
12053                         ) || '</datafield>' INTO uri_datafield
12054               FROM  oils_xpath_table(
12055                         'id',
12056                         'marc',
12057                         'biblio.record_entry',
12058                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
12059                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
12060                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
12061                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
12062                         'id=' || source_record
12063                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
12064
12065             uri_text := uri_text || uri_datafield;
12066         END LOOP;
12067
12068         IF uri_text <> '' THEN
12069             UPDATE  biblio.record_entry
12070               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
12071               WHERE id = target_record;
12072         END IF;
12073
12074     END IF;
12075
12076     -- Find and move metarecords to the target record
12077     SELECT  INTO metarec *
12078       FROM  metabib.metarecord
12079       WHERE master_record = source_record;
12080
12081     IF FOUND THEN
12082         UPDATE  metabib.metarecord
12083           SET   master_record = target_record,
12084             mods = NULL
12085           WHERE id = metarec.id;
12086
12087         moved_objects := moved_objects + 1;
12088     END IF;
12089
12090     -- Find call numbers attached to the source ...
12091     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
12092
12093         SELECT  INTO target_cn *
12094           FROM  asset.call_number
12095           WHERE label = source_cn.label
12096             AND owning_lib = source_cn.owning_lib
12097             AND record = target_record;
12098
12099         -- ... and if there's a conflicting one on the target ...
12100         IF FOUND THEN
12101
12102             -- ... move the copies to that, and ...
12103             UPDATE  asset.copy
12104               SET   call_number = target_cn.id
12105               WHERE call_number = source_cn.id;
12106
12107             -- ... move V holds to the move-target call number
12108             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
12109
12110                 UPDATE  action.hold_request
12111                   SET   target = target_cn.id
12112                   WHERE id = hold.id;
12113
12114                 moved_objects := moved_objects + 1;
12115             END LOOP;
12116
12117         -- ... if not ...
12118         ELSE
12119             -- ... just move the call number to the target record
12120             UPDATE  asset.call_number
12121               SET   record = target_record
12122               WHERE id = source_cn.id;
12123         END IF;
12124
12125         moved_objects := moved_objects + 1;
12126     END LOOP;
12127
12128     -- Find T holds targeting the source record ...
12129     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
12130
12131         -- ... and move them to the target record
12132         UPDATE  action.hold_request
12133           SET   target = target_record
12134           WHERE id = hold.id;
12135
12136         moved_objects := moved_objects + 1;
12137     END LOOP;
12138
12139     -- Find serial records targeting the source record ...
12140     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
12141         -- ... and move them to the target record
12142         UPDATE  serial.record_entry
12143           SET   record = target_record
12144           WHERE id = ser_rec.id;
12145
12146         moved_objects := moved_objects + 1;
12147     END LOOP;
12148
12149     -- Finally, "delete" the source record
12150     DELETE FROM biblio.record_entry WHERE id = source_record;
12151
12152     -- That's all, folks!
12153     RETURN moved_objects;
12154 END;
12155 $func$ LANGUAGE plpgsql;
12156
12157 CREATE OR REPLACE FUNCTION acq.transfer_fund(
12158         old_fund   IN INT,
12159         old_amount IN NUMERIC,     -- in currency of old fund
12160         new_fund   IN INT,
12161         new_amount IN NUMERIC,     -- in currency of new fund
12162         user_id    IN INT,
12163         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
12164         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
12165 ) RETURNS VOID AS $$
12166 /* -------------------------------------------------------------------------------
12167
12168 Function to transfer money from one fund to another.
12169
12170 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12171 negative amount for the old (losing) fund and a positive amount for the new
12172 (gaining) fund.  In some cases there may be more than one such pair of entries
12173 in order to pull the money from different funding sources, or more specifically
12174 from different funding source credits.  For each such pair there is also an
12175 entry in acq.fund_transfer.
12176
12177 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12178 choose a funding source for the transferred money to come from.  This choice
12179 must meet two constraints, so far as possible:
12180
12181 1. The amount transferred from a given funding source must not exceed the
12182 amount allocated to the old fund by the funding source.  To that end we
12183 compare the amount being transferred to the amount allocated.
12184
12185 2. We shouldn't transfer money that has already been spent or encumbered, as
12186 defined by the funding attribution process.  We attribute expenses to the
12187 oldest funding source credits first.  In order to avoid transferring that
12188 attributed money, we reverse the priority, transferring from the newest funding
12189 source credits first.  There can be no guarantee that this approach will
12190 avoid overcommitting a fund, but no other approach can do any better.
12191
12192 In this context the age of a funding source credit is defined by the
12193 deadline_date for credits with deadline_dates, and by the effective_date for
12194 credits without deadline_dates, with the proviso that credits with deadline_dates
12195 are all considered "older" than those without.
12196
12197 ----------
12198
12199 In the signature for this function, there is one last parameter commented out,
12200 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12201 driving the main loop has an OR clause commented out, which references the
12202 funding_source_in parameter.
12203
12204 If these lines are uncommented, this function will allow the user optionally to
12205 restrict a fund transfer to a specified funding source.  If the source
12206 parameter is left NULL, then there will be no such restriction.
12207
12208 ------------------------------------------------------------------------------- */ 
12209 DECLARE
12210         same_currency      BOOLEAN;
12211         currency_ratio     NUMERIC;
12212         old_fund_currency  TEXT;
12213         old_remaining      NUMERIC;  -- in currency of old fund
12214         new_fund_currency  TEXT;
12215         new_fund_active    BOOLEAN;
12216         new_remaining      NUMERIC;  -- in currency of new fund
12217         curr_old_amt       NUMERIC;  -- in currency of old fund
12218         curr_new_amt       NUMERIC;  -- in currency of new fund
12219         source_addition    NUMERIC;  -- in currency of funding source
12220         source_deduction   NUMERIC;  -- in currency of funding source
12221         orig_allocated_amt NUMERIC;  -- in currency of funding source
12222         allocated_amt      NUMERIC;  -- in currency of fund
12223         source             RECORD;
12224 BEGIN
12225         --
12226         -- Sanity checks
12227         --
12228         IF old_fund IS NULL THEN
12229                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12230         END IF;
12231         --
12232         IF old_amount IS NULL THEN
12233                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12234         END IF;
12235         --
12236         -- The new fund and its amount must be both NULL or both not NULL.
12237         --
12238         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12239                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12240         END IF;
12241         --
12242         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12243                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12244         END IF;
12245         --
12246         IF user_id IS NULL THEN
12247                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12248         END IF;
12249         --
12250         -- Initialize the amounts to be transferred, each denominated
12251         -- in the currency of its respective fund.  They will be
12252         -- reduced on each iteration of the loop.
12253         --
12254         old_remaining := old_amount;
12255         new_remaining := new_amount;
12256         --
12257         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12258         --      old_amount, old_fund, new_amount, new_fund;
12259         --
12260         -- Get the currency types of the old and new funds.
12261         --
12262         SELECT
12263                 currency_type
12264         INTO
12265                 old_fund_currency
12266         FROM
12267                 acq.fund
12268         WHERE
12269                 id = old_fund;
12270         --
12271         IF old_fund_currency IS NULL THEN
12272                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12273         END IF;
12274         --
12275         IF new_fund IS NOT NULL THEN
12276                 SELECT
12277                         currency_type,
12278                         active
12279                 INTO
12280                         new_fund_currency,
12281                         new_fund_active
12282                 FROM
12283                         acq.fund
12284                 WHERE
12285                         id = new_fund;
12286                 --
12287                 IF new_fund_currency IS NULL THEN
12288                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12289                 ELSIF NOT new_fund_active THEN
12290                         --
12291                         -- No point in putting money into a fund from whence you can't spend it
12292                         --
12293                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12294                 END IF;
12295                 --
12296                 IF new_amount = old_amount THEN
12297                         same_currency := true;
12298                         currency_ratio := 1;
12299                 ELSE
12300                         --
12301                         -- We'll have to translate currency between funds.  We presume that
12302                         -- the calling code has already applied an appropriate exchange rate,
12303                         -- so we'll apply the same conversion to each sub-transfer.
12304                         --
12305                         same_currency := false;
12306                         currency_ratio := new_amount / old_amount;
12307                 END IF;
12308         END IF;
12309         --
12310         -- Identify the funding source(s) from which we want to transfer the money.
12311         -- The principle is that we want to transfer the newest money first, because
12312         -- we spend the oldest money first.  The priority for spending is defined
12313         -- by a sort of the view acq.ordered_funding_source_credit.
12314         --
12315         FOR source in
12316                 SELECT
12317                         ofsc.id,
12318                         ofsc.funding_source,
12319                         ofsc.amount,
12320                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12321                                 AS converted_amt,
12322                         fs.currency_type
12323                 FROM
12324                         acq.ordered_funding_source_credit AS ofsc,
12325                         acq.funding_source fs
12326                 WHERE
12327                         ofsc.funding_source = fs.id
12328                         and ofsc.funding_source IN
12329                         (
12330                                 SELECT funding_source
12331                                 FROM acq.fund_allocation
12332                                 WHERE fund = old_fund
12333                         )
12334                         -- and
12335                         -- (
12336                         --      ofsc.funding_source = funding_source_in
12337                         --      OR funding_source_in IS NULL
12338                         -- )
12339                 ORDER BY
12340                         ofsc.sort_priority desc,
12341                         ofsc.sort_date desc,
12342                         ofsc.id desc
12343         LOOP
12344                 --
12345                 -- Determine how much money the old fund got from this funding source,
12346                 -- denominated in the currency types of the source and of the fund.
12347                 -- This result may reflect transfers from previous iterations.
12348                 --
12349                 SELECT
12350                         COALESCE( sum( amount ), 0 ),
12351                         COALESCE( sum( amount )
12352                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12353                 INTO
12354                         orig_allocated_amt,     -- in currency of the source
12355                         allocated_amt           -- in currency of the old fund
12356                 FROM
12357                         acq.fund_allocation
12358                 WHERE
12359                         fund = old_fund
12360                         and funding_source = source.funding_source;
12361                 --      
12362                 -- Determine how much to transfer from this credit, in the currency
12363                 -- of the fund.   Begin with the amount remaining to be attributed:
12364                 --
12365                 curr_old_amt := old_remaining;
12366                 --
12367                 -- Can't attribute more than was allocated from the fund:
12368                 --
12369                 IF curr_old_amt > allocated_amt THEN
12370                         curr_old_amt := allocated_amt;
12371                 END IF;
12372                 --
12373                 -- Can't attribute more than the amount of the current credit:
12374                 --
12375                 IF curr_old_amt > source.converted_amt THEN
12376                         curr_old_amt := source.converted_amt;
12377                 END IF;
12378                 --
12379                 curr_old_amt := trunc( curr_old_amt, 2 );
12380                 --
12381                 old_remaining := old_remaining - curr_old_amt;
12382                 --
12383                 -- Determine the amount to be deducted, if any,
12384                 -- from the old allocation.
12385                 --
12386                 IF old_remaining > 0 THEN
12387                         --
12388                         -- In this case we're using the whole allocation, so use that
12389                         -- amount directly instead of applying a currency translation
12390                         -- and thereby inviting round-off errors.
12391                         --
12392                         source_deduction := - orig_allocated_amt;
12393                 ELSE 
12394                         source_deduction := trunc(
12395                                 ( - curr_old_amt ) *
12396                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12397                                 2 );
12398                 END IF;
12399                 --
12400                 IF source_deduction <> 0 THEN
12401                         --
12402                         -- Insert negative allocation for old fund in fund_allocation,
12403                         -- converted into the currency of the funding source
12404                         --
12405                         INSERT INTO acq.fund_allocation (
12406                                 funding_source,
12407                                 fund,
12408                                 amount,
12409                                 allocator,
12410                                 note
12411                         ) VALUES (
12412                                 source.funding_source,
12413                                 old_fund,
12414                                 source_deduction,
12415                                 user_id,
12416                                 'Transfer to fund ' || new_fund
12417                         );
12418                 END IF;
12419                 --
12420                 IF new_fund IS NOT NULL THEN
12421                         --
12422                         -- Determine how much to add to the new fund, in
12423                         -- its currency, and how much remains to be added:
12424                         --
12425                         IF same_currency THEN
12426                                 curr_new_amt := curr_old_amt;
12427                         ELSE
12428                                 IF old_remaining = 0 THEN
12429                                         --
12430                                         -- This is the last iteration, so nothing should be left
12431                                         --
12432                                         curr_new_amt := new_remaining;
12433                                         new_remaining := 0;
12434                                 ELSE
12435                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12436                                         new_remaining := new_remaining - curr_new_amt;
12437                                 END IF;
12438                         END IF;
12439                         --
12440                         -- Determine how much to add, if any,
12441                         -- to the new fund's allocation.
12442                         --
12443                         IF old_remaining > 0 THEN
12444                                 --
12445                                 -- In this case we're using the whole allocation, so use that amount
12446                                 -- amount directly instead of applying a currency translation and
12447                                 -- thereby inviting round-off errors.
12448                                 --
12449                                 source_addition := orig_allocated_amt;
12450                         ELSIF source.currency_type = old_fund_currency THEN
12451                                 --
12452                                 -- In this case we don't need a round trip currency translation,
12453                                 -- thereby inviting round-off errors:
12454                                 --
12455                                 source_addition := curr_old_amt;
12456                         ELSE 
12457                                 source_addition := trunc(
12458                                         curr_new_amt *
12459                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12460                                         2 );
12461                         END IF;
12462                         --
12463                         IF source_addition <> 0 THEN
12464                                 --
12465                                 -- Insert positive allocation for new fund in fund_allocation,
12466                                 -- converted to the currency of the founding source
12467                                 --
12468                                 INSERT INTO acq.fund_allocation (
12469                                         funding_source,
12470                                         fund,
12471                                         amount,
12472                                         allocator,
12473                                         note
12474                                 ) VALUES (
12475                                         source.funding_source,
12476                                         new_fund,
12477                                         source_addition,
12478                                         user_id,
12479                                         'Transfer from fund ' || old_fund
12480                                 );
12481                         END IF;
12482                 END IF;
12483                 --
12484                 IF trunc( curr_old_amt, 2 ) <> 0
12485                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12486                         --
12487                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12488                         --
12489                         INSERT INTO acq.fund_transfer (
12490                                 src_fund,
12491                                 src_amount,
12492                                 dest_fund,
12493                                 dest_amount,
12494                                 transfer_user,
12495                                 note,
12496                                 funding_source_credit
12497                         ) VALUES (
12498                                 old_fund,
12499                                 trunc( curr_old_amt, 2 ),
12500                                 new_fund,
12501                                 trunc( curr_new_amt, 2 ),
12502                                 user_id,
12503                                 xfer_note,
12504                                 source.id
12505                         );
12506                 END IF;
12507                 --
12508                 if old_remaining <= 0 THEN
12509                         EXIT;                   -- Nothing more to be transferred
12510                 END IF;
12511         END LOOP;
12512 END;
12513 $$ LANGUAGE plpgsql;
12514
12515 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
12516         old_year INTEGER,
12517         user_id INTEGER,
12518         org_unit_id INTEGER
12519 ) RETURNS VOID AS $$
12520 DECLARE
12521 --
12522 new_id      INT;
12523 old_fund    RECORD;
12524 org_found   BOOLEAN;
12525 --
12526 BEGIN
12527         --
12528         -- Sanity checks
12529         --
12530         IF old_year IS NULL THEN
12531                 RAISE EXCEPTION 'Input year argument is NULL';
12532         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12533                 RAISE EXCEPTION 'Input year is out of range';
12534         END IF;
12535         --
12536         IF user_id IS NULL THEN
12537                 RAISE EXCEPTION 'Input user id argument is NULL';
12538         END IF;
12539         --
12540         IF org_unit_id IS NULL THEN
12541                 RAISE EXCEPTION 'Org unit id argument is NULL';
12542         ELSE
12543                 SELECT TRUE INTO org_found
12544                 FROM actor.org_unit
12545                 WHERE id = org_unit_id;
12546                 --
12547                 IF org_found IS NULL THEN
12548                         RAISE EXCEPTION 'Org unit id is invalid';
12549                 END IF;
12550         END IF;
12551         --
12552         -- Loop over the applicable funds
12553         --
12554         FOR old_fund in SELECT * FROM acq.fund
12555         WHERE
12556                 year = old_year
12557                 AND propagate
12558                 AND org = org_unit_id
12559         LOOP
12560                 BEGIN
12561                         INSERT INTO acq.fund (
12562                                 org,
12563                                 name,
12564                                 year,
12565                                 currency_type,
12566                                 code,
12567                                 rollover,
12568                                 propagate,
12569                                 balance_warning_percent,
12570                                 balance_stop_percent
12571                         ) VALUES (
12572                                 old_fund.org,
12573                                 old_fund.name,
12574                                 old_year + 1,
12575                                 old_fund.currency_type,
12576                                 old_fund.code,
12577                                 old_fund.rollover,
12578                                 true,
12579                                 old_fund.balance_warning_percent,
12580                                 old_fund.balance_stop_percent
12581                         )
12582                         RETURNING id INTO new_id;
12583                 EXCEPTION
12584                         WHEN unique_violation THEN
12585                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12586                                 CONTINUE;
12587                 END;
12588                 --RAISE NOTICE 'Propagating fund % to fund %',
12589                 --      old_fund.code, new_id;
12590         END LOOP;
12591 END;
12592 $$ LANGUAGE plpgsql;
12593
12594 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12595         old_year INTEGER,
12596         user_id INTEGER,
12597         org_unit_id INTEGER
12598 ) RETURNS VOID AS $$
12599 DECLARE
12600 --
12601 new_id      INT;
12602 old_fund    RECORD;
12603 org_found   BOOLEAN;
12604 --
12605 BEGIN
12606         --
12607         -- Sanity checks
12608         --
12609         IF old_year IS NULL THEN
12610                 RAISE EXCEPTION 'Input year argument is NULL';
12611         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12612                 RAISE EXCEPTION 'Input year is out of range';
12613         END IF;
12614         --
12615         IF user_id IS NULL THEN
12616                 RAISE EXCEPTION 'Input user id argument is NULL';
12617         END IF;
12618         --
12619         IF org_unit_id IS NULL THEN
12620                 RAISE EXCEPTION 'Org unit id argument is NULL';
12621         ELSE
12622                 SELECT TRUE INTO org_found
12623                 FROM actor.org_unit
12624                 WHERE id = org_unit_id;
12625                 --
12626                 IF org_found IS NULL THEN
12627                         RAISE EXCEPTION 'Org unit id is invalid';
12628                 END IF;
12629         END IF;
12630         --
12631         -- Loop over the applicable funds
12632         --
12633         FOR old_fund in SELECT * FROM acq.fund
12634         WHERE
12635                 year = old_year
12636                 AND propagate
12637                 AND org in (
12638                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12639                 )
12640         LOOP
12641                 BEGIN
12642                         INSERT INTO acq.fund (
12643                                 org,
12644                                 name,
12645                                 year,
12646                                 currency_type,
12647                                 code,
12648                                 rollover,
12649                                 propagate,
12650                                 balance_warning_percent,
12651                                 balance_stop_percent
12652                         ) VALUES (
12653                                 old_fund.org,
12654                                 old_fund.name,
12655                                 old_year + 1,
12656                                 old_fund.currency_type,
12657                                 old_fund.code,
12658                                 old_fund.rollover,
12659                                 true,
12660                                 old_fund.balance_warning_percent,
12661                                 old_fund.balance_stop_percent
12662                         )
12663                         RETURNING id INTO new_id;
12664                 EXCEPTION
12665                         WHEN unique_violation THEN
12666                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12667                                 CONTINUE;
12668                 END;
12669                 --RAISE NOTICE 'Propagating fund % to fund %',
12670                 --      old_fund.code, new_id;
12671         END LOOP;
12672 END;
12673 $$ LANGUAGE plpgsql;
12674
12675 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12676         old_year INTEGER,
12677         user_id INTEGER,
12678         org_unit_id INTEGER
12679 ) RETURNS VOID AS $$
12680 DECLARE
12681 --
12682 new_fund    INT;
12683 new_year    INT := old_year + 1;
12684 org_found   BOOL;
12685 xfer_amount NUMERIC;
12686 roll_fund   RECORD;
12687 deb         RECORD;
12688 detail      RECORD;
12689 --
12690 BEGIN
12691         --
12692         -- Sanity checks
12693         --
12694         IF old_year IS NULL THEN
12695                 RAISE EXCEPTION 'Input year argument is NULL';
12696     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12697         RAISE EXCEPTION 'Input year is out of range';
12698         END IF;
12699         --
12700         IF user_id IS NULL THEN
12701                 RAISE EXCEPTION 'Input user id argument is NULL';
12702         END IF;
12703         --
12704         IF org_unit_id IS NULL THEN
12705                 RAISE EXCEPTION 'Org unit id argument is NULL';
12706         ELSE
12707                 --
12708                 -- Validate the org unit
12709                 --
12710                 SELECT TRUE
12711                 INTO org_found
12712                 FROM actor.org_unit
12713                 WHERE id = org_unit_id;
12714                 --
12715                 IF org_found IS NULL THEN
12716                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12717                 END IF;
12718         END IF;
12719         --
12720         -- Loop over the propagable funds to identify the details
12721         -- from the old fund plus the id of the new one, if it exists.
12722         --
12723         FOR roll_fund in
12724         SELECT
12725             oldf.id AS old_fund,
12726             oldf.org,
12727             oldf.name,
12728             oldf.currency_type,
12729             oldf.code,
12730                 oldf.rollover,
12731             newf.id AS new_fund_id
12732         FROM
12733         acq.fund AS oldf
12734         LEFT JOIN acq.fund AS newf
12735                 ON ( oldf.code = newf.code )
12736         WHERE
12737                     oldf.org = org_unit_id
12738                 and oldf.year = old_year
12739                 and oldf.propagate
12740         and newf.year = new_year
12741         LOOP
12742                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12743                 --
12744                 IF roll_fund.new_fund_id IS NULL THEN
12745                         --
12746                         -- The old fund hasn't been propagated yet.  Propagate it now.
12747                         --
12748                         INSERT INTO acq.fund (
12749                                 org,
12750                                 name,
12751                                 year,
12752                                 currency_type,
12753                                 code,
12754                                 rollover,
12755                                 propagate,
12756                                 balance_warning_percent,
12757                                 balance_stop_percent
12758                         ) VALUES (
12759                                 roll_fund.org,
12760                                 roll_fund.name,
12761                                 new_year,
12762                                 roll_fund.currency_type,
12763                                 roll_fund.code,
12764                                 true,
12765                                 true,
12766                                 roll_fund.balance_warning_percent,
12767                                 roll_fund.balance_stop_percent
12768                         )
12769                         RETURNING id INTO new_fund;
12770                 ELSE
12771                         new_fund = roll_fund.new_fund_id;
12772                 END IF;
12773                 --
12774                 -- Determine the amount to transfer
12775                 --
12776                 SELECT amount
12777                 INTO xfer_amount
12778                 FROM acq.fund_spent_balance
12779                 WHERE fund = roll_fund.old_fund;
12780                 --
12781                 IF xfer_amount <> 0 THEN
12782                         IF roll_fund.rollover THEN
12783                                 --
12784                                 -- Transfer balance from old fund to new
12785                                 --
12786                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12787                                 --
12788                                 PERFORM acq.transfer_fund(
12789                                         roll_fund.old_fund,
12790                                         xfer_amount,
12791                                         new_fund,
12792                                         xfer_amount,
12793                                         user_id,
12794                                         'Rollover'
12795                                 );
12796                         ELSE
12797                                 --
12798                                 -- Transfer balance from old fund to the void
12799                                 --
12800                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12801                                 --
12802                                 PERFORM acq.transfer_fund(
12803                                         roll_fund.old_fund,
12804                                         xfer_amount,
12805                                         NULL,
12806                                         NULL,
12807                                         user_id,
12808                                         'Rollover'
12809                                 );
12810                         END IF;
12811                 END IF;
12812                 --
12813                 IF roll_fund.rollover THEN
12814                         --
12815                         -- Move any lineitems from the old fund to the new one
12816                         -- where the associated debit is an encumbrance.
12817                         --
12818                         -- Any other tables tying expenditure details to funds should
12819                         -- receive similar treatment.  At this writing there are none.
12820                         --
12821                         UPDATE acq.lineitem_detail
12822                         SET fund = new_fund
12823                         WHERE
12824                         fund = roll_fund.old_fund -- this condition may be redundant
12825                         AND fund_debit in
12826                         (
12827                                 SELECT id
12828                                 FROM acq.fund_debit
12829                                 WHERE
12830                                 fund = roll_fund.old_fund
12831                                 AND encumbrance
12832                         );
12833                         --
12834                         -- Move encumbrance debits from the old fund to the new fund
12835                         --
12836                         UPDATE acq.fund_debit
12837                         SET fund = new_fund
12838                         wHERE
12839                                 fund = roll_fund.old_fund
12840                                 AND encumbrance;
12841                 END IF;
12842                 --
12843                 -- Mark old fund as inactive, now that we've closed it
12844                 --
12845                 UPDATE acq.fund
12846                 SET active = FALSE
12847                 WHERE id = roll_fund.old_fund;
12848         END LOOP;
12849 END;
12850 $$ LANGUAGE plpgsql;
12851
12852 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12853         old_year INTEGER,
12854         user_id INTEGER,
12855         org_unit_id INTEGER
12856 ) RETURNS VOID AS $$
12857 DECLARE
12858 --
12859 new_fund    INT;
12860 new_year    INT := old_year + 1;
12861 org_found   BOOL;
12862 xfer_amount NUMERIC;
12863 roll_fund   RECORD;
12864 deb         RECORD;
12865 detail      RECORD;
12866 --
12867 BEGIN
12868         --
12869         -- Sanity checks
12870         --
12871         IF old_year IS NULL THEN
12872                 RAISE EXCEPTION 'Input year argument is NULL';
12873     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12874         RAISE EXCEPTION 'Input year is out of range';
12875         END IF;
12876         --
12877         IF user_id IS NULL THEN
12878                 RAISE EXCEPTION 'Input user id argument is NULL';
12879         END IF;
12880         --
12881         IF org_unit_id IS NULL THEN
12882                 RAISE EXCEPTION 'Org unit id argument is NULL';
12883         ELSE
12884                 --
12885                 -- Validate the org unit
12886                 --
12887                 SELECT TRUE
12888                 INTO org_found
12889                 FROM actor.org_unit
12890                 WHERE id = org_unit_id;
12891                 --
12892                 IF org_found IS NULL THEN
12893                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12894                 END IF;
12895         END IF;
12896         --
12897         -- Loop over the propagable funds to identify the details
12898         -- from the old fund plus the id of the new one, if it exists.
12899         --
12900         FOR roll_fund in
12901         SELECT
12902             oldf.id AS old_fund,
12903             oldf.org,
12904             oldf.name,
12905             oldf.currency_type,
12906             oldf.code,
12907                 oldf.rollover,
12908             newf.id AS new_fund_id
12909         FROM
12910         acq.fund AS oldf
12911         LEFT JOIN acq.fund AS newf
12912                 ON ( oldf.code = newf.code )
12913         WHERE
12914                     oldf.year = old_year
12915                 AND oldf.propagate
12916         AND newf.year = new_year
12917                 AND oldf.org in (
12918                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12919                 )
12920         LOOP
12921                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12922                 --
12923                 IF roll_fund.new_fund_id IS NULL THEN
12924                         --
12925                         -- The old fund hasn't been propagated yet.  Propagate it now.
12926                         --
12927                         INSERT INTO acq.fund (
12928                                 org,
12929                                 name,
12930                                 year,
12931                                 currency_type,
12932                                 code,
12933                                 rollover,
12934                                 propagate,
12935                                 balance_warning_percent,
12936                                 balance_stop_percent
12937                         ) VALUES (
12938                                 roll_fund.org,
12939                                 roll_fund.name,
12940                                 new_year,
12941                                 roll_fund.currency_type,
12942                                 roll_fund.code,
12943                                 true,
12944                                 true,
12945                                 roll_fund.balance_warning_percent,
12946                                 roll_fund.balance_stop_percent
12947                         )
12948                         RETURNING id INTO new_fund;
12949                 ELSE
12950                         new_fund = roll_fund.new_fund_id;
12951                 END IF;
12952                 --
12953                 -- Determine the amount to transfer
12954                 --
12955                 SELECT amount
12956                 INTO xfer_amount
12957                 FROM acq.fund_spent_balance
12958                 WHERE fund = roll_fund.old_fund;
12959                 --
12960                 IF xfer_amount <> 0 THEN
12961                         IF roll_fund.rollover THEN
12962                                 --
12963                                 -- Transfer balance from old fund to new
12964                                 --
12965                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12966                                 --
12967                                 PERFORM acq.transfer_fund(
12968                                         roll_fund.old_fund,
12969                                         xfer_amount,
12970                                         new_fund,
12971                                         xfer_amount,
12972                                         user_id,
12973                                         'Rollover'
12974                                 );
12975                         ELSE
12976                                 --
12977                                 -- Transfer balance from old fund to the void
12978                                 --
12979                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12980                                 --
12981                                 PERFORM acq.transfer_fund(
12982                                         roll_fund.old_fund,
12983                                         xfer_amount,
12984                                         NULL,
12985                                         NULL,
12986                                         user_id,
12987                                         'Rollover'
12988                                 );
12989                         END IF;
12990                 END IF;
12991                 --
12992                 IF roll_fund.rollover THEN
12993                         --
12994                         -- Move any lineitems from the old fund to the new one
12995                         -- where the associated debit is an encumbrance.
12996                         --
12997                         -- Any other tables tying expenditure details to funds should
12998                         -- receive similar treatment.  At this writing there are none.
12999                         --
13000                         UPDATE acq.lineitem_detail
13001                         SET fund = new_fund
13002                         WHERE
13003                         fund = roll_fund.old_fund -- this condition may be redundant
13004                         AND fund_debit in
13005                         (
13006                                 SELECT id
13007                                 FROM acq.fund_debit
13008                                 WHERE
13009                                 fund = roll_fund.old_fund
13010                                 AND encumbrance
13011                         );
13012                         --
13013                         -- Move encumbrance debits from the old fund to the new fund
13014                         --
13015                         UPDATE acq.fund_debit
13016                         SET fund = new_fund
13017                         wHERE
13018                                 fund = roll_fund.old_fund
13019                                 AND encumbrance;
13020                 END IF;
13021                 --
13022                 -- Mark old fund as inactive, now that we've closed it
13023                 --
13024                 UPDATE acq.fund
13025                 SET active = FALSE
13026                 WHERE id = roll_fund.old_fund;
13027         END LOOP;
13028 END;
13029 $$ LANGUAGE plpgsql;
13030
13031 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
13032     SELECT regexp_replace($1, ',', '', 'g');
13033 $$ LANGUAGE SQL STRICT IMMUTABLE;
13034
13035 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
13036     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
13037 $$ LANGUAGE SQL STRICT IMMUTABLE;
13038
13039 CREATE TABLE acq.distribution_formula_application (
13040     id BIGSERIAL PRIMARY KEY,
13041     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
13042     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
13043     formula INT NOT NULL
13044         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
13045     lineitem INT NOT NULL
13046         REFERENCES acq.lineitem( id )
13047                 ON DELETE CASCADE
13048                 DEFERRABLE INITIALLY DEFERRED
13049 );
13050
13051 CREATE INDEX acqdfa_df_idx
13052     ON acq.distribution_formula_application(formula);
13053 CREATE INDEX acqdfa_li_idx
13054     ON acq.distribution_formula_application(lineitem);
13055 CREATE INDEX acqdfa_creator_idx
13056     ON acq.distribution_formula_application(creator);
13057
13058 CREATE TABLE acq.user_request_type (
13059     id      SERIAL  PRIMARY KEY,
13060     label   TEXT    NOT NULL UNIQUE -- i18n-ize
13061 );
13062
13063 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
13064 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
13065 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
13066 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
13067 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
13068
13069 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
13070
13071 CREATE TABLE acq.cancel_reason (
13072         id            SERIAL            PRIMARY KEY,
13073         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
13074                                         DEFERRABLE INITIALLY DEFERRED,
13075         label         TEXT              NOT NULL,
13076         description   TEXT              NOT NULL,
13077         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
13078         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
13079 );
13080
13081 -- Reserve ids 1-999 for stock reasons
13082 -- Reserve ids 1000-1999 for EDI reasons
13083 -- 2000+ are available for staff to create
13084
13085 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
13086
13087 CREATE TABLE acq.user_request (
13088     id                  SERIAL  PRIMARY KEY,
13089     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
13090     hold                BOOL    NOT NULL DEFAULT TRUE,
13091
13092     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
13093     holdable_formats    TEXT,           -- nullable, for use in hold creation
13094     phone_notify        TEXT,
13095     email_notify        BOOL    NOT NULL DEFAULT TRUE,
13096     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
13097     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
13098     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
13099     need_before         TIMESTAMPTZ,    -- don't create holds after this
13100     max_fee             TEXT,
13101
13102     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
13103     isxn                TEXT,
13104     title               TEXT,
13105     volume              TEXT,
13106     author              TEXT,
13107     article_title       TEXT,
13108     article_pages       TEXT,
13109     publisher           TEXT,
13110     location            TEXT,
13111     pubdate             TEXT,
13112     mentioned           TEXT,
13113     other_info          TEXT,
13114         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
13115                                              DEFERRABLE INITIALLY DEFERRED
13116 );
13117
13118 CREATE TABLE acq.lineitem_alert_text (
13119         id               SERIAL         PRIMARY KEY,
13120         code             TEXT           NOT NULL,
13121         description      TEXT,
13122         owning_lib       INT            NOT NULL
13123                                         REFERENCES actor.org_unit(id)
13124                                         DEFERRABLE INITIALLY DEFERRED,
13125         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
13126 );
13127
13128 ALTER TABLE acq.lineitem_note
13129         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
13130                                          DEFERRABLE INITIALLY DEFERRED;
13131
13132 -- add ON DELETE CASCADE clause
13133
13134 ALTER TABLE acq.lineitem_note
13135         DROP CONSTRAINT lineitem_note_lineitem_fkey;
13136
13137 ALTER TABLE acq.lineitem_note
13138         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13139                 ON DELETE CASCADE
13140                 DEFERRABLE INITIALLY DEFERRED;
13141
13142 ALTER TABLE acq.lineitem_note
13143         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13144
13145 CREATE TABLE acq.invoice_method (
13146     code    TEXT    PRIMARY KEY,
13147     name    TEXT    NOT NULL -- i18n-ize
13148 );
13149 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
13150 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
13151
13152 CREATE TABLE acq.invoice_payment_method (
13153         code      TEXT     PRIMARY KEY,
13154         name      TEXT     NOT NULL
13155 );
13156
13157 CREATE TABLE acq.invoice (
13158     id             SERIAL      PRIMARY KEY,
13159     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
13160     provider       INT         NOT NULL REFERENCES acq.provider (id),
13161     shipper        INT         NOT NULL REFERENCES acq.provider (id),
13162     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13163     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
13164     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
13165     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
13166         payment_auth   TEXT,
13167         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
13168                                    DEFERRABLE INITIALLY DEFERRED,
13169         note           TEXT,
13170     complete       BOOL        NOT NULL DEFAULT FALSE,
13171     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13172 );
13173
13174 CREATE TABLE acq.invoice_entry (
13175     id              SERIAL      PRIMARY KEY,
13176     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13177     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13178     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13179     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13180     phys_item_count INT, -- and how many did staff count
13181     note            TEXT,
13182     billed_per_item BOOL,
13183     cost_billed     NUMERIC(8,2),
13184     actual_cost     NUMERIC(8,2),
13185         amount_paid     NUMERIC (8,2)
13186 );
13187
13188 CREATE TABLE acq.invoice_item_type (
13189     code    TEXT    PRIMARY KEY,
13190     name    TEXT    NOT NULL, -- i18n-ize
13191         prorate BOOL    NOT NULL DEFAULT FALSE
13192 );
13193
13194 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13195 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13196 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13197 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13198 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13199 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13200
13201 CREATE TABLE acq.po_item (
13202         id              SERIAL      PRIMARY KEY,
13203         purchase_order  INT         REFERENCES acq.purchase_order (id)
13204                                     ON UPDATE CASCADE ON DELETE SET NULL
13205                                     DEFERRABLE INITIALLY DEFERRED,
13206         fund_debit      INT         REFERENCES acq.fund_debit (id)
13207                                     DEFERRABLE INITIALLY DEFERRED,
13208         inv_item_type   TEXT        NOT NULL
13209                                     REFERENCES acq.invoice_item_type (code)
13210                                     DEFERRABLE INITIALLY DEFERRED,
13211         title           TEXT,
13212         author          TEXT,
13213         note            TEXT,
13214         estimated_cost  NUMERIC(8,2),
13215         fund            INT         REFERENCES acq.fund (id)
13216                                     DEFERRABLE INITIALLY DEFERRED,
13217         target          BIGINT
13218 );
13219
13220 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13221     id              SERIAL      PRIMARY KEY,
13222     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13223     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13224     fund_debit      INT         REFERENCES acq.fund_debit (id),
13225     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13226     title           TEXT,
13227     author          TEXT,
13228     note            TEXT,
13229     cost_billed     NUMERIC(8,2),
13230     actual_cost     NUMERIC(8,2),
13231     fund            INT         REFERENCES acq.fund (id)
13232                                 DEFERRABLE INITIALLY DEFERRED,
13233     amount_paid     NUMERIC (8,2),
13234     po_item         INT         REFERENCES acq.po_item (id)
13235                                 DEFERRABLE INITIALLY DEFERRED,
13236     target          BIGINT
13237 );
13238
13239 CREATE TABLE acq.edi_message (
13240     id               SERIAL          PRIMARY KEY,
13241     account          INTEGER         REFERENCES acq.edi_account(id)
13242                                      DEFERRABLE INITIALLY DEFERRED,
13243     remote_file      TEXT,
13244     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13245     translate_time   TIMESTAMPTZ,
13246     process_time     TIMESTAMPTZ,
13247     error_time       TIMESTAMPTZ,
13248     status           TEXT            NOT NULL DEFAULT 'new'
13249                                      CONSTRAINT status_value CHECK
13250                                      ( status IN (
13251                                         'new',          -- needs to be translated
13252                                         'translated',   -- needs to be processed
13253                                         'trans_error',  -- error in translation step
13254                                         'processed',    -- needs to have remote_file deleted
13255                                         'proc_error',   -- error in processing step
13256                                         'delete_error', -- error in deletion
13257                                         'retry',        -- need to retry
13258                                         'complete'      -- done
13259                                      )),
13260     edi              TEXT,
13261     jedi             TEXT,
13262     error            TEXT,
13263     purchase_order   INT             REFERENCES acq.purchase_order
13264                                      DEFERRABLE INITIALLY DEFERRED,
13265     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13266                                      CHECK ( message_type IN (
13267                                         'ORDERS',
13268                                         'ORDRSP',
13269                                         'INVOIC',
13270                                         'OSTENQ',
13271                                         'OSTRPT'
13272                                      ))
13273 );
13274
13275 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13276
13277 ALTER TABLE acq.provider_address
13278         ADD COLUMN fax_phone TEXT;
13279
13280 ALTER TABLE acq.provider_contact_address
13281         ADD COLUMN fax_phone TEXT;
13282
13283 CREATE TABLE acq.provider_note (
13284     id      SERIAL              PRIMARY KEY,
13285     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13286     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13287     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13288     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13289     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13290     value       TEXT            NOT NULL
13291 );
13292 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13293 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13294 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13295
13296 -- For each fund: the total allocation from all sources, in the
13297 -- currency of the fund (or 0 if there are no allocations)
13298
13299 CREATE VIEW acq.all_fund_allocation_total AS
13300 SELECT
13301     f.id AS fund,
13302     COALESCE( SUM( a.amount * acq.exchange_ratio(
13303         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13304     AS amount
13305 FROM
13306     acq.fund f
13307         LEFT JOIN acq.fund_allocation a
13308             ON a.fund = f.id
13309         LEFT JOIN acq.funding_source s
13310             ON a.funding_source = s.id
13311 GROUP BY
13312     f.id;
13313
13314 -- For every fund: the total encumbrances (or 0 if none),
13315 -- in the currency of the fund.
13316
13317 CREATE VIEW acq.all_fund_encumbrance_total AS
13318 SELECT
13319         f.id AS fund,
13320         COALESCE( encumb.amount, 0 ) AS amount
13321 FROM
13322         acq.fund AS f
13323                 LEFT JOIN (
13324                         SELECT
13325                                 fund,
13326                                 sum( amount ) AS amount
13327                         FROM
13328                                 acq.fund_debit
13329                         WHERE
13330                                 encumbrance
13331                         GROUP BY fund
13332                 ) AS encumb
13333                         ON f.id = encumb.fund;
13334
13335 -- For every fund: the total spent (or 0 if none),
13336 -- in the currency of the fund.
13337
13338 CREATE VIEW acq.all_fund_spent_total AS
13339 SELECT
13340     f.id AS fund,
13341     COALESCE( spent.amount, 0 ) AS amount
13342 FROM
13343     acq.fund AS f
13344         LEFT JOIN (
13345             SELECT
13346                 fund,
13347                 sum( amount ) AS amount
13348             FROM
13349                 acq.fund_debit
13350             WHERE
13351                 NOT encumbrance
13352             GROUP BY fund
13353         ) AS spent
13354             ON f.id = spent.fund;
13355
13356 -- For each fund: the amount not yet spent, in the currency
13357 -- of the fund.  May include encumbrances.
13358
13359 CREATE VIEW acq.all_fund_spent_balance AS
13360 SELECT
13361         c.fund,
13362         c.amount - d.amount AS amount
13363 FROM acq.all_fund_allocation_total c
13364     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13365
13366 -- For each fund: the amount neither spent nor encumbered,
13367 -- in the currency of the fund
13368
13369 CREATE VIEW acq.all_fund_combined_balance AS
13370 SELECT
13371      a.fund,
13372      a.amount - COALESCE( c.amount, 0 ) AS amount
13373 FROM
13374      acq.all_fund_allocation_total a
13375         LEFT OUTER JOIN (
13376             SELECT
13377                 fund,
13378                 SUM( amount ) AS amount
13379             FROM
13380                 acq.fund_debit
13381             GROUP BY
13382                 fund
13383         ) AS c USING ( fund );
13384
13385 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 $$
13386 DECLARE
13387         suffix TEXT;
13388         bucket_row RECORD;
13389         picklist_row RECORD;
13390         queue_row RECORD;
13391         folder_row RECORD;
13392 BEGIN
13393
13394     -- do some initial cleanup 
13395     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13396     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13397     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13398
13399     -- actor.*
13400     IF del_cards THEN
13401         DELETE FROM actor.card where usr = src_usr;
13402     ELSE
13403         IF deactivate_cards THEN
13404             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13405         END IF;
13406         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13407     END IF;
13408
13409
13410     IF del_addrs THEN
13411         DELETE FROM actor.usr_address WHERE usr = src_usr;
13412     ELSE
13413         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13414     END IF;
13415
13416     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13417     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13418     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13419     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13420     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13421
13422     -- permission.*
13423     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13424     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13425     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13426     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13427
13428
13429     -- container.*
13430         
13431         -- For each *_bucket table: transfer every bucket belonging to src_usr
13432         -- into the custody of dest_usr.
13433         --
13434         -- In order to avoid colliding with an existing bucket owned by
13435         -- the destination user, append the source user's id (in parenthesese)
13436         -- to the name.  If you still get a collision, add successive
13437         -- spaces to the name and keep trying until you succeed.
13438         --
13439         FOR bucket_row in
13440                 SELECT id, name
13441                 FROM   container.biblio_record_entry_bucket
13442                 WHERE  owner = src_usr
13443         LOOP
13444                 suffix := ' (' || src_usr || ')';
13445                 LOOP
13446                         BEGIN
13447                                 UPDATE  container.biblio_record_entry_bucket
13448                                 SET     owner = dest_usr, name = name || suffix
13449                                 WHERE   id = bucket_row.id;
13450                         EXCEPTION WHEN unique_violation THEN
13451                                 suffix := suffix || ' ';
13452                                 CONTINUE;
13453                         END;
13454                         EXIT;
13455                 END LOOP;
13456         END LOOP;
13457
13458         FOR bucket_row in
13459                 SELECT id, name
13460                 FROM   container.call_number_bucket
13461                 WHERE  owner = src_usr
13462         LOOP
13463                 suffix := ' (' || src_usr || ')';
13464                 LOOP
13465                         BEGIN
13466                                 UPDATE  container.call_number_bucket
13467                                 SET     owner = dest_usr, name = name || suffix
13468                                 WHERE   id = bucket_row.id;
13469                         EXCEPTION WHEN unique_violation THEN
13470                                 suffix := suffix || ' ';
13471                                 CONTINUE;
13472                         END;
13473                         EXIT;
13474                 END LOOP;
13475         END LOOP;
13476
13477         FOR bucket_row in
13478                 SELECT id, name
13479                 FROM   container.copy_bucket
13480                 WHERE  owner = src_usr
13481         LOOP
13482                 suffix := ' (' || src_usr || ')';
13483                 LOOP
13484                         BEGIN
13485                                 UPDATE  container.copy_bucket
13486                                 SET     owner = dest_usr, name = name || suffix
13487                                 WHERE   id = bucket_row.id;
13488                         EXCEPTION WHEN unique_violation THEN
13489                                 suffix := suffix || ' ';
13490                                 CONTINUE;
13491                         END;
13492                         EXIT;
13493                 END LOOP;
13494         END LOOP;
13495
13496         FOR bucket_row in
13497                 SELECT id, name
13498                 FROM   container.user_bucket
13499                 WHERE  owner = src_usr
13500         LOOP
13501                 suffix := ' (' || src_usr || ')';
13502                 LOOP
13503                         BEGIN
13504                                 UPDATE  container.user_bucket
13505                                 SET     owner = dest_usr, name = name || suffix
13506                                 WHERE   id = bucket_row.id;
13507                         EXCEPTION WHEN unique_violation THEN
13508                                 suffix := suffix || ' ';
13509                                 CONTINUE;
13510                         END;
13511                         EXIT;
13512                 END LOOP;
13513         END LOOP;
13514
13515         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13516
13517     -- vandelay.*
13518         -- transfer queues the same way we transfer buckets (see above)
13519         FOR queue_row in
13520                 SELECT id, name
13521                 FROM   vandelay.queue
13522                 WHERE  owner = src_usr
13523         LOOP
13524                 suffix := ' (' || src_usr || ')';
13525                 LOOP
13526                         BEGIN
13527                                 UPDATE  vandelay.queue
13528                                 SET     owner = dest_usr, name = name || suffix
13529                                 WHERE   id = queue_row.id;
13530                         EXCEPTION WHEN unique_violation THEN
13531                                 suffix := suffix || ' ';
13532                                 CONTINUE;
13533                         END;
13534                         EXIT;
13535                 END LOOP;
13536         END LOOP;
13537
13538     -- money.*
13539     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13540     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13541     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13542     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13543     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13544
13545     -- action.*
13546     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13547     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13548     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13549
13550     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13551     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13552     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13553     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13554
13555     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13556     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13557     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13558     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13559     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13560
13561     -- acq.*
13562     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13563         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13564
13565         -- transfer picklists the same way we transfer buckets (see above)
13566         FOR picklist_row in
13567                 SELECT id, name
13568                 FROM   acq.picklist
13569                 WHERE  owner = src_usr
13570         LOOP
13571                 suffix := ' (' || src_usr || ')';
13572                 LOOP
13573                         BEGIN
13574                                 UPDATE  acq.picklist
13575                                 SET     owner = dest_usr, name = name || suffix
13576                                 WHERE   id = picklist_row.id;
13577                         EXCEPTION WHEN unique_violation THEN
13578                                 suffix := suffix || ' ';
13579                                 CONTINUE;
13580                         END;
13581                         EXIT;
13582                 END LOOP;
13583         END LOOP;
13584
13585     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13586     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13587     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13588     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13589     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13590     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13591     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13592     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13593
13594     -- asset.*
13595     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13596     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13597     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13598     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13599     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13600     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13601
13602     -- serial.*
13603     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13604     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13605
13606     -- reporter.*
13607     -- It's not uncommon to define the reporter schema in a replica 
13608     -- DB only, so don't assume these tables exist in the write DB.
13609     BEGIN
13610         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13611     EXCEPTION WHEN undefined_table THEN
13612         -- do nothing
13613     END;
13614     BEGIN
13615         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13616     EXCEPTION WHEN undefined_table THEN
13617         -- do nothing
13618     END;
13619     BEGIN
13620         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13621     EXCEPTION WHEN undefined_table THEN
13622         -- do nothing
13623     END;
13624     BEGIN
13625                 -- transfer folders the same way we transfer buckets (see above)
13626                 FOR folder_row in
13627                         SELECT id, name
13628                         FROM   reporter.template_folder
13629                         WHERE  owner = src_usr
13630                 LOOP
13631                         suffix := ' (' || src_usr || ')';
13632                         LOOP
13633                                 BEGIN
13634                                         UPDATE  reporter.template_folder
13635                                         SET     owner = dest_usr, name = name || suffix
13636                                         WHERE   id = folder_row.id;
13637                                 EXCEPTION WHEN unique_violation THEN
13638                                         suffix := suffix || ' ';
13639                                         CONTINUE;
13640                                 END;
13641                                 EXIT;
13642                         END LOOP;
13643                 END LOOP;
13644     EXCEPTION WHEN undefined_table THEN
13645         -- do nothing
13646     END;
13647     BEGIN
13648                 -- transfer folders the same way we transfer buckets (see above)
13649                 FOR folder_row in
13650                         SELECT id, name
13651                         FROM   reporter.report_folder
13652                         WHERE  owner = src_usr
13653                 LOOP
13654                         suffix := ' (' || src_usr || ')';
13655                         LOOP
13656                                 BEGIN
13657                                         UPDATE  reporter.report_folder
13658                                         SET     owner = dest_usr, name = name || suffix
13659                                         WHERE   id = folder_row.id;
13660                                 EXCEPTION WHEN unique_violation THEN
13661                                         suffix := suffix || ' ';
13662                                         CONTINUE;
13663                                 END;
13664                                 EXIT;
13665                         END LOOP;
13666                 END LOOP;
13667     EXCEPTION WHEN undefined_table THEN
13668         -- do nothing
13669     END;
13670     BEGIN
13671                 -- transfer folders the same way we transfer buckets (see above)
13672                 FOR folder_row in
13673                         SELECT id, name
13674                         FROM   reporter.output_folder
13675                         WHERE  owner = src_usr
13676                 LOOP
13677                         suffix := ' (' || src_usr || ')';
13678                         LOOP
13679                                 BEGIN
13680                                         UPDATE  reporter.output_folder
13681                                         SET     owner = dest_usr, name = name || suffix
13682                                         WHERE   id = folder_row.id;
13683                                 EXCEPTION WHEN unique_violation THEN
13684                                         suffix := suffix || ' ';
13685                                         CONTINUE;
13686                                 END;
13687                                 EXIT;
13688                         END LOOP;
13689                 END LOOP;
13690     EXCEPTION WHEN undefined_table THEN
13691         -- do nothing
13692     END;
13693
13694     -- Finally, delete the source user
13695     DELETE FROM actor.usr WHERE id = src_usr;
13696
13697 END;
13698 $$ LANGUAGE plpgsql;
13699
13700 -- The "add" trigger functions should protect against existing NULLed values, just in case
13701 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13702 BEGIN
13703     IF NOT NEW.voided THEN
13704         UPDATE  money.materialized_billable_xact_summary
13705           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13706             last_billing_ts = NEW.billing_ts,
13707             last_billing_note = NEW.note,
13708             last_billing_type = NEW.billing_type,
13709             balance_owed = balance_owed + NEW.amount
13710           WHERE id = NEW.xact;
13711     END IF;
13712
13713     RETURN NEW;
13714 END;
13715 $$ LANGUAGE PLPGSQL;
13716
13717 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13718 BEGIN
13719     IF NOT NEW.voided THEN
13720         UPDATE  money.materialized_billable_xact_summary
13721           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13722             last_payment_ts = NEW.payment_ts,
13723             last_payment_note = NEW.note,
13724             last_payment_type = TG_ARGV[0],
13725             balance_owed = balance_owed - NEW.amount
13726           WHERE id = NEW.xact;
13727     END IF;
13728
13729     RETURN NEW;
13730 END;
13731 $$ LANGUAGE PLPGSQL;
13732
13733 -- Refresh the mat view with the corrected underlying view
13734 TRUNCATE money.materialized_billable_xact_summary;
13735 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13736
13737 -- Now redefine the view as a window onto the materialized view
13738 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13739     SELECT * FROM money.materialized_billable_xact_summary;
13740
13741 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13742     user_id    IN INTEGER,
13743     perm_code  IN TEXT
13744 )
13745 RETURNS SETOF INTEGER AS $$
13746 --
13747 -- Return a set of all the org units for which a given user has a given
13748 -- permission, granted directly (not through inheritance from a parent
13749 -- org unit).
13750 --
13751 -- The permissions apply to a minimum depth of the org unit hierarchy,
13752 -- for the org unit(s) to which the user is assigned.  (They also apply
13753 -- to the subordinates of those org units, but we don't report the
13754 -- subordinates here.)
13755 --
13756 -- For purposes of this function, the permission.usr_work_ou_map table
13757 -- defines which users belong to which org units.  I.e. we ignore the
13758 -- home_ou column of actor.usr.
13759 --
13760 -- The result set may contain duplicates, which should be eliminated
13761 -- by a DISTINCT clause.
13762 --
13763 DECLARE
13764     b_super       BOOLEAN;
13765     n_perm        INTEGER;
13766     n_min_depth   INTEGER;
13767     n_work_ou     INTEGER;
13768     n_curr_ou     INTEGER;
13769     n_depth       INTEGER;
13770     n_curr_depth  INTEGER;
13771 BEGIN
13772     --
13773     -- Check for superuser
13774     --
13775     SELECT INTO b_super
13776         super_user
13777     FROM
13778         actor.usr
13779     WHERE
13780         id = user_id;
13781     --
13782     IF NOT FOUND THEN
13783         return;             -- No user?  No permissions.
13784     ELSIF b_super THEN
13785         --
13786         -- Super user has all permissions everywhere
13787         --
13788         FOR n_work_ou IN
13789             SELECT
13790                 id
13791             FROM
13792                 actor.org_unit
13793             WHERE
13794                 parent_ou IS NULL
13795         LOOP
13796             RETURN NEXT n_work_ou;
13797         END LOOP;
13798         RETURN;
13799     END IF;
13800     --
13801     -- Translate the permission name
13802     -- to a numeric permission id
13803     --
13804     SELECT INTO n_perm
13805         id
13806     FROM
13807         permission.perm_list
13808     WHERE
13809         code = perm_code;
13810     --
13811     IF NOT FOUND THEN
13812         RETURN;               -- No such permission
13813     END IF;
13814     --
13815     -- Find the highest-level org unit (i.e. the minimum depth)
13816     -- to which the permission is applied for this user
13817     --
13818     -- This query is modified from the one in permission.usr_perms().
13819     --
13820     SELECT INTO n_min_depth
13821         min( depth )
13822     FROM    (
13823         SELECT depth
13824           FROM permission.usr_perm_map upm
13825          WHERE upm.usr = user_id
13826            AND (upm.perm = n_perm OR upm.perm = -1)
13827                     UNION
13828         SELECT  gpm.depth
13829           FROM  permission.grp_perm_map gpm
13830           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13831             AND gpm.grp IN (
13832                SELECT   (permission.grp_ancestors(
13833                     (SELECT profile FROM actor.usr WHERE id = user_id)
13834                 )).id
13835             )
13836                     UNION
13837         SELECT  p.depth
13838           FROM  permission.grp_perm_map p
13839           WHERE (p.perm = n_perm OR p.perm = -1)
13840             AND p.grp IN (
13841                 SELECT (permission.grp_ancestors(m.grp)).id
13842                 FROM   permission.usr_grp_map m
13843                 WHERE  m.usr = user_id
13844             )
13845     ) AS x;
13846     --
13847     IF NOT FOUND THEN
13848         RETURN;                -- No such permission for this user
13849     END IF;
13850     --
13851     -- Identify the org units to which the user is assigned.  Note that
13852     -- we pay no attention to the home_ou column in actor.usr.
13853     --
13854     FOR n_work_ou IN
13855         SELECT
13856             work_ou
13857         FROM
13858             permission.usr_work_ou_map
13859         WHERE
13860             usr = user_id
13861     LOOP            -- For each org unit to which the user is assigned
13862         --
13863         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13864         -- We take it on faith that this depth agrees with the actual hierarchy
13865         -- defined in actor.org_unit.
13866         --
13867         SELECT INTO n_depth
13868             type.depth
13869         FROM
13870             actor.org_unit_type type
13871                 INNER JOIN actor.org_unit ou
13872                     ON ( ou.ou_type = type.id )
13873         WHERE
13874             ou.id = n_work_ou;
13875         --
13876         IF NOT FOUND THEN
13877             CONTINUE;        -- Maybe raise exception?
13878         END IF;
13879         --
13880         -- Compare the depth of the work org unit to the
13881         -- minimum depth, and branch accordingly
13882         --
13883         IF n_depth = n_min_depth THEN
13884             --
13885             -- The org unit is at the right depth, so return it.
13886             --
13887             RETURN NEXT n_work_ou;
13888         ELSIF n_depth > n_min_depth THEN
13889             --
13890             -- Traverse the org unit tree toward the root,
13891             -- until you reach the minimum depth determined above
13892             --
13893             n_curr_depth := n_depth;
13894             n_curr_ou := n_work_ou;
13895             WHILE n_curr_depth > n_min_depth LOOP
13896                 SELECT INTO n_curr_ou
13897                     parent_ou
13898                 FROM
13899                     actor.org_unit
13900                 WHERE
13901                     id = n_curr_ou;
13902                 --
13903                 IF FOUND THEN
13904                     n_curr_depth := n_curr_depth - 1;
13905                 ELSE
13906                     --
13907                     -- This can happen only if the hierarchy defined in
13908                     -- actor.org_unit is corrupted, or out of sync with
13909                     -- the depths defined in actor.org_unit_type.
13910                     -- Maybe we should raise an exception here, instead
13911                     -- of silently ignoring the problem.
13912                     --
13913                     n_curr_ou = NULL;
13914                     EXIT;
13915                 END IF;
13916             END LOOP;
13917             --
13918             IF n_curr_ou IS NOT NULL THEN
13919                 RETURN NEXT n_curr_ou;
13920             END IF;
13921         ELSE
13922             --
13923             -- The permission applies only at a depth greater than the work org unit.
13924             -- Use connectby() to find all dependent org units at the specified depth.
13925             --
13926             FOR n_curr_ou IN
13927                 SELECT ou::INTEGER
13928                 FROM connectby(
13929                         'actor.org_unit',         -- table name
13930                         'id',                     -- key column
13931                         'parent_ou',              -- recursive foreign key
13932                         n_work_ou::TEXT,          -- id of starting point
13933                         (n_min_depth - n_depth)   -- max depth to search, relative
13934                     )                             --   to starting point
13935                     AS t(
13936                         ou text,            -- dependent org unit
13937                         parent_ou text,     -- (ignore)
13938                         level int           -- depth relative to starting point
13939                     )
13940                 WHERE
13941                     level = n_min_depth - n_depth
13942             LOOP
13943                 RETURN NEXT n_curr_ou;
13944             END LOOP;
13945         END IF;
13946         --
13947     END LOOP;
13948     --
13949     RETURN;
13950     --
13951 END;
13952 $$ LANGUAGE 'plpgsql';
13953
13954 ALTER TABLE acq.purchase_order
13955         ADD COLUMN cancel_reason INT
13956                 REFERENCES acq.cancel_reason( id )
13957             DEFERRABLE INITIALLY DEFERRED,
13958         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
13959
13960 -- Build the history table and lifecycle view
13961 -- for acq.purchase_order
13962
13963 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
13964
13965 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
13966
13967 ALTER TABLE acq.lineitem
13968         ADD COLUMN cancel_reason INT
13969                 REFERENCES acq.cancel_reason( id )
13970             DEFERRABLE INITIALLY DEFERRED,
13971         ADD COLUMN estimated_unit_price NUMERIC,
13972         ADD COLUMN claim_policy INT
13973                 REFERENCES acq.claim_policy
13974                 DEFERRABLE INITIALLY DEFERRED,
13975         ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
13976
13977 -- Build the history table and lifecycle view
13978 -- for acq.lineitem
13979
13980 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
13981 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
13982
13983 ALTER TABLE acq.lineitem_detail
13984         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
13985                                             DEFERRABLE INITIALLY DEFERRED;
13986
13987 ALTER TABLE acq.lineitem_detail
13988         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
13989
13990 ALTER TABLE acq.lineitem_detail
13991         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13992                 ON DELETE CASCADE
13993                 DEFERRABLE INITIALLY DEFERRED;
13994
13995 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
13996
13997 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
13998         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
13999
14000 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14001         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
14002
14003 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14004
14005     use MARC::Record;
14006     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14007     use strict;
14008
14009     my $target_xml = shift;
14010     my $source_xml = shift;
14011     my $field_spec = shift;
14012
14013     my $target_r = MARC::Record->new_from_xml( $target_xml );
14014     my $source_r = MARC::Record->new_from_xml( $source_xml );
14015
14016     return $target_xml unless ($target_r && $source_r);
14017
14018     my @field_list = split(',', $field_spec);
14019
14020     my %fields;
14021     for my $f (@field_list) {
14022         $f =~ s/^\s*//; $f =~ s/\s*$//;
14023         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14024             my $field = $1;
14025             $field =~ s/\s+//;
14026             my $sf = $2;
14027             $sf =~ s/\s+//;
14028             my $match = $3;
14029             $match =~ s/^\s*//; $match =~ s/\s*$//;
14030             $fields{$field} = { sf => [ split('', $sf) ] };
14031             if ($match) {
14032                 my ($msf,$mre) = split('~', $match);
14033                 if (length($msf) > 0 and length($mre) > 0) {
14034                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14035                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14036                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14037                 }
14038             }
14039         }
14040     }
14041
14042     for my $f ( keys %fields) {
14043         if ( @{$fields{$f}{sf}} ) {
14044             for my $from_field ($source_r->field( $f )) {
14045                 my @tos = $target_r->field( $f );
14046                 if (!@tos) {
14047                     my @new_fields = map { $_->clone } $source_r->field( $f );
14048                     $target_r->insert_fields_ordered( @new_fields );
14049                 } else {
14050                     for my $to_field (@tos) {
14051                         if (exists($fields{$f}{match})) {
14052                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14053                         }
14054                         my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
14055                         $to_field->add_subfields( @new_sf );
14056                     }
14057                 }
14058             }
14059         } else {
14060             my @new_fields = map { $_->clone } $source_r->field( $f );
14061             $target_r->insert_fields_ordered( @new_fields );
14062         }
14063     }
14064
14065     $target_xml = $target_r->as_xml_record;
14066     $target_xml =~ s/^<\?.+?\?>$//mo;
14067     $target_xml =~ s/\n//sgo;
14068     $target_xml =~ s/>\s+</></sgo;
14069
14070     return $target_xml;
14071
14072 $_$ LANGUAGE PLPERLU;
14073
14074 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14075
14076     use MARC::Record;
14077     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14078     use strict;
14079
14080     my $xml = shift;
14081     my $r = MARC::Record->new_from_xml( $xml );
14082
14083     return $xml unless ($r);
14084
14085     my $field_spec = shift;
14086     my @field_list = split(',', $field_spec);
14087
14088     my %fields;
14089     for my $f (@field_list) {
14090         $f =~ s/^\s*//; $f =~ s/\s*$//;
14091         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14092             my $field = $1;
14093             $field =~ s/\s+//;
14094             my $sf = $2;
14095             $sf =~ s/\s+//;
14096             my $match = $3;
14097             $match =~ s/^\s*//; $match =~ s/\s*$//;
14098             $fields{$field} = { sf => [ split('', $sf) ] };
14099             if ($match) {
14100                 my ($msf,$mre) = split('~', $match);
14101                 if (length($msf) > 0 and length($mre) > 0) {
14102                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14103                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14104                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14105                 }
14106             }
14107         }
14108     }
14109
14110     for my $f ( keys %fields) {
14111         for my $to_field ($r->field( $f )) {
14112             if (exists($fields{$f}{match})) {
14113                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14114             }
14115
14116             if ( @{$fields{$f}{sf}} ) {
14117                 $to_field->delete_subfield(code => $fields{$f}{sf});
14118             } else {
14119                 $r->delete_field( $to_field );
14120             }
14121         }
14122     }
14123
14124     $xml = $r->as_xml_record;
14125     $xml =~ s/^<\?.+?\?>$//mo;
14126     $xml =~ s/\n//sgo;
14127     $xml =~ s/>\s+</></sgo;
14128
14129     return $xml;
14130
14131 $_$ LANGUAGE PLPERLU;
14132
14133 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14134     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
14135 $_$ LANGUAGE SQL;
14136
14137 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14138     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
14139 $_$ LANGUAGE SQL;
14140
14141 CREATE VIEW action.unfulfilled_hold_max_loop AS
14142         SELECT  hold,
14143                 max(count) AS max
14144         FROM    action.unfulfilled_hold_loops
14145         GROUP BY 1;
14146
14147 ALTER TABLE acq.lineitem_attr
14148         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
14149
14150 ALTER TABLE acq.lineitem_attr
14151         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14152                 ON DELETE CASCADE
14153                 DEFERRABLE INITIALLY DEFERRED;
14154
14155 ALTER TABLE acq.po_note
14156         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
14157
14158 CREATE TABLE vandelay.merge_profile (
14159     id              BIGSERIAL   PRIMARY KEY,
14160     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
14161     name            TEXT        NOT NULL,
14162     add_spec        TEXT,
14163     replace_spec    TEXT,
14164     strip_spec      TEXT,
14165     preserve_spec   TEXT,
14166     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14167     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))
14168 );
14169
14170 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14171 DECLARE
14172     attr        RECORD;
14173     attr_def    RECORD;
14174     eg_rec      RECORD;
14175     id_value    TEXT;
14176     exact_id    BIGINT;
14177 BEGIN
14178
14179     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14180
14181     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14182
14183     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14184         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14185
14186         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14187             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14188             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14189             IF exact_id IS NOT NULL THEN
14190                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14191             END IF;
14192         END IF;
14193     END IF;
14194
14195     IF exact_id IS NULL THEN
14196         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
14197
14198             -- All numbers? check for an id match
14199             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14200                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14201                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14202                 END LOOP;
14203             END IF;
14204
14205             -- Looks like an ISBN? check for an isbn match
14206             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14207                 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
14208                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14209                     IF FOUND THEN
14210                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14211                     END IF;
14212                 END LOOP;
14213
14214                 -- subcheck for isbn-as-tcn
14215                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14216                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14217                 END LOOP;
14218             END IF;
14219
14220             -- check for an OCLC tcn_value match
14221             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14222                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14223                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14224                 END LOOP;
14225             END IF;
14226
14227             -- check for a direct tcn_value match
14228             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14229                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14230             END LOOP;
14231
14232             -- check for a direct item barcode match
14233             FOR eg_rec IN
14234                     SELECT  DISTINCT b.*
14235                       FROM  biblio.record_entry b
14236                             JOIN asset.call_number cn ON (cn.record = b.id)
14237                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14238                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14239             LOOP
14240                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14241             END LOOP;
14242
14243         END LOOP;
14244     END IF;
14245
14246     RETURN NULL;
14247 END;
14248 $func$ LANGUAGE PLPGSQL;
14249
14250 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 $_$
14251     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14252 $_$ LANGUAGE SQL;
14253
14254 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14255 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14256 DECLARE
14257     output              vandelay.compile_profile%ROWTYPE;
14258     profile             vandelay.merge_profile%ROWTYPE;
14259     profile_tmpl        TEXT;
14260     profile_tmpl_owner  TEXT;
14261     add_rule            TEXT := '';
14262     strip_rule          TEXT := '';
14263     replace_rule        TEXT := '';
14264     preserve_rule       TEXT := '';
14265
14266 BEGIN
14267
14268     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14269     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14270
14271     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14272         SELECT  p.* INTO profile
14273           FROM  vandelay.merge_profile p
14274                 JOIN actor.org_unit u ON (u.id = p.owner)
14275           WHERE p.name = profile_tmpl
14276                 AND u.shortname = profile_tmpl_owner;
14277
14278         IF profile.id IS NOT NULL THEN
14279             add_rule := COALESCE(profile.add_spec,'');
14280             strip_rule := COALESCE(profile.strip_spec,'');
14281             replace_rule := COALESCE(profile.replace_spec,'');
14282             preserve_rule := COALESCE(profile.preserve_spec,'');
14283         END IF;
14284     END IF;
14285
14286     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
14287     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
14288     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
14289     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
14290
14291     output.add_rule := BTRIM(add_rule,',');
14292     output.replace_rule := BTRIM(replace_rule,',');
14293     output.strip_rule := BTRIM(strip_rule,',');
14294     output.preserve_rule := BTRIM(preserve_rule,',');
14295
14296     RETURN output;
14297 END;
14298 $_$ LANGUAGE PLPGSQL;
14299
14300 -- Template-based marc munging functions
14301 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14302 DECLARE
14303     merge_profile   vandelay.merge_profile%ROWTYPE;
14304     dyn_profile     vandelay.compile_profile%ROWTYPE;
14305     editor_string   TEXT;
14306     editor_id       INT;
14307     source_marc     TEXT;
14308     target_marc     TEXT;
14309     eg_marc         TEXT;
14310     replace_rule    TEXT;
14311     match_count     INT;
14312 BEGIN
14313
14314     SELECT  b.marc INTO eg_marc
14315       FROM  biblio.record_entry b
14316       WHERE b.id = eg_id
14317       LIMIT 1;
14318
14319     IF eg_marc IS NULL OR v_marc IS NULL THEN
14320         -- RAISE NOTICE 'no marc for template or bib record';
14321         RETURN FALSE;
14322     END IF;
14323
14324     dyn_profile := vandelay.compile_profile( v_marc );
14325
14326     IF merge_profile_id IS NOT NULL THEN
14327         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14328         IF FOUND THEN
14329             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14330             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14331             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14332             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14333         END IF;
14334     END IF;
14335
14336     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14337         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14338         RETURN FALSE;
14339     END IF;
14340
14341     IF dyn_profile.replace_rule <> '' THEN
14342         source_marc = v_marc;
14343         target_marc = eg_marc;
14344         replace_rule = dyn_profile.replace_rule;
14345     ELSE
14346         source_marc = eg_marc;
14347         target_marc = v_marc;
14348         replace_rule = dyn_profile.preserve_rule;
14349     END IF;
14350
14351     UPDATE  biblio.record_entry
14352       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14353       WHERE id = eg_id;
14354
14355     IF NOT FOUND THEN
14356         -- RAISE NOTICE 'update of biblio.record_entry failed';
14357         RETURN FALSE;
14358     END IF;
14359
14360     RETURN TRUE;
14361
14362 END;
14363 $$ LANGUAGE PLPGSQL;
14364
14365 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14366     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14367 $$ LANGUAGE SQL;
14368
14369 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14370 DECLARE
14371     merge_profile   vandelay.merge_profile%ROWTYPE;
14372     dyn_profile     vandelay.compile_profile%ROWTYPE;
14373     editor_string   TEXT;
14374     editor_id       INT;
14375     source_marc     TEXT;
14376     target_marc     TEXT;
14377     eg_marc         TEXT;
14378     v_marc          TEXT;
14379     replace_rule    TEXT;
14380     match_count     INT;
14381 BEGIN
14382
14383     SELECT  q.marc INTO v_marc
14384       FROM  vandelay.queued_record q
14385             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14386       LIMIT 1;
14387
14388     IF v_marc IS NULL THEN
14389         -- RAISE NOTICE 'no marc for vandelay or bib record';
14390         RETURN FALSE;
14391     END IF;
14392
14393     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14394         UPDATE  vandelay.queued_bib_record
14395           SET   imported_as = eg_id,
14396                 import_time = NOW()
14397           WHERE id = import_id;
14398
14399         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14400
14401         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14402             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14403
14404             IF editor_id IS NULL THEN
14405                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14406             END IF;
14407
14408             IF editor_id IS NOT NULL THEN
14409                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14410             END IF;
14411         END IF;
14412
14413         RETURN TRUE;
14414     END IF;
14415
14416     -- RAISE NOTICE 'update of biblio.record_entry failed';
14417
14418     RETURN FALSE;
14419
14420 END;
14421 $$ LANGUAGE PLPGSQL;
14422
14423 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14424 DECLARE
14425     eg_id           BIGINT;
14426     match_count     INT;
14427     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14428 BEGIN
14429
14430     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14431
14432     IF FOUND THEN
14433         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14434         RETURN FALSE;
14435     END IF;
14436
14437     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14438
14439     IF match_count <> 1 THEN
14440         -- RAISE NOTICE 'not an exact match';
14441         RETURN FALSE;
14442     END IF;
14443
14444     SELECT  d.* INTO match_attr
14445       FROM  vandelay.bib_attr_definition d
14446             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14447             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14448       WHERE m.queued_record = import_id;
14449
14450     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14451         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14452         RETURN FALSE;
14453     END IF;
14454
14455     SELECT  m.eg_record INTO eg_id
14456       FROM  vandelay.bib_match m
14457       WHERE m.queued_record = import_id
14458       LIMIT 1;
14459
14460     IF eg_id IS NULL THEN
14461         RETURN FALSE;
14462     END IF;
14463
14464     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14465 END;
14466 $$ LANGUAGE PLPGSQL;
14467
14468 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14469 DECLARE
14470     queued_record   vandelay.queued_bib_record%ROWTYPE;
14471 BEGIN
14472
14473     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14474
14475         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14476             RETURN NEXT queued_record.id;
14477         END IF;
14478
14479     END LOOP;
14480
14481     RETURN;
14482
14483 END;
14484 $$ LANGUAGE PLPGSQL;
14485
14486 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14487     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14488 $$ LANGUAGE SQL;
14489
14490 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14491 DECLARE
14492     merge_profile   vandelay.merge_profile%ROWTYPE;
14493     dyn_profile     vandelay.compile_profile%ROWTYPE;
14494     source_marc     TEXT;
14495     target_marc     TEXT;
14496     eg_marc         TEXT;
14497     v_marc          TEXT;
14498     replace_rule    TEXT;
14499     match_count     INT;
14500 BEGIN
14501
14502     SELECT  b.marc INTO eg_marc
14503       FROM  authority.record_entry b
14504             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14505       LIMIT 1;
14506
14507     SELECT  q.marc INTO v_marc
14508       FROM  vandelay.queued_record q
14509             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14510       LIMIT 1;
14511
14512     IF eg_marc IS NULL OR v_marc IS NULL THEN
14513         -- RAISE NOTICE 'no marc for vandelay or authority record';
14514         RETURN FALSE;
14515     END IF;
14516
14517     dyn_profile := vandelay.compile_profile( v_marc );
14518
14519     IF merge_profile_id IS NOT NULL THEN
14520         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14521         IF FOUND THEN
14522             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14523             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14524             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14525             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14526         END IF;
14527     END IF;
14528
14529     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14530         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14531         RETURN FALSE;
14532     END IF;
14533
14534     IF dyn_profile.replace_rule <> '' THEN
14535         source_marc = v_marc;
14536         target_marc = eg_marc;
14537         replace_rule = dyn_profile.replace_rule;
14538     ELSE
14539         source_marc = eg_marc;
14540         target_marc = v_marc;
14541         replace_rule = dyn_profile.preserve_rule;
14542     END IF;
14543
14544     UPDATE  authority.record_entry
14545       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14546       WHERE id = eg_id;
14547
14548     IF FOUND THEN
14549         UPDATE  vandelay.queued_authority_record
14550           SET   imported_as = eg_id,
14551                 import_time = NOW()
14552           WHERE id = import_id;
14553         RETURN TRUE;
14554     END IF;
14555
14556     -- RAISE NOTICE 'update of authority.record_entry failed';
14557
14558     RETURN FALSE;
14559
14560 END;
14561 $$ LANGUAGE PLPGSQL;
14562
14563 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14564 DECLARE
14565     eg_id           BIGINT;
14566     match_count     INT;
14567 BEGIN
14568     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14569
14570     IF match_count <> 1 THEN
14571         -- RAISE NOTICE 'not an exact match';
14572         RETURN FALSE;
14573     END IF;
14574
14575     SELECT  m.eg_record INTO eg_id
14576       FROM  vandelay.authority_match m
14577       WHERE m.queued_record = import_id
14578       LIMIT 1;
14579
14580     IF eg_id IS NULL THEN
14581         RETURN FALSE;
14582     END IF;
14583
14584     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14585 END;
14586 $$ LANGUAGE PLPGSQL;
14587
14588 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14589 DECLARE
14590     queued_record   vandelay.queued_authority_record%ROWTYPE;
14591 BEGIN
14592
14593     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14594
14595         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14596             RETURN NEXT queued_record.id;
14597         END IF;
14598
14599     END LOOP;
14600
14601     RETURN;
14602
14603 END;
14604 $$ LANGUAGE PLPGSQL;
14605
14606 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14607     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14608 $$ LANGUAGE SQL;
14609
14610 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14611 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14612 DECLARE
14613     eg_tcn          TEXT;
14614     eg_tcn_source   TEXT;
14615     output          vandelay.tcn_data%ROWTYPE;
14616 BEGIN
14617
14618     -- 001/003
14619     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14620     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14621
14622         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14623         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14624             eg_tcn_source := 'System Local';
14625         END IF;
14626
14627         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14628
14629         IF NOT FOUND THEN
14630             output.used := FALSE;
14631         ELSE
14632             output.used := TRUE;
14633         END IF;
14634
14635         output.tcn := eg_tcn;
14636         output.tcn_source := eg_tcn_source;
14637         RETURN NEXT output;
14638
14639     END IF;
14640
14641     -- 901 ab
14642     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14643     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14644
14645         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14646         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14647             eg_tcn_source := 'System Local';
14648         END IF;
14649
14650         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14651
14652         IF NOT FOUND THEN
14653             output.used := FALSE;
14654         ELSE
14655             output.used := TRUE;
14656         END IF;
14657
14658         output.tcn := eg_tcn;
14659         output.tcn_source := eg_tcn_source;
14660         RETURN NEXT output;
14661
14662     END IF;
14663
14664     -- 039 ab
14665     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14666     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14667
14668         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14669         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14670             eg_tcn_source := 'System Local';
14671         END IF;
14672
14673         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14674
14675         IF NOT FOUND THEN
14676             output.used := FALSE;
14677         ELSE
14678             output.used := TRUE;
14679         END IF;
14680
14681         output.tcn := eg_tcn;
14682         output.tcn_source := eg_tcn_source;
14683         RETURN NEXT output;
14684
14685     END IF;
14686
14687     -- 020 a
14688     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14689     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14690
14691         eg_tcn_source := 'ISBN';
14692
14693         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14694
14695         IF NOT FOUND THEN
14696             output.used := FALSE;
14697         ELSE
14698             output.used := TRUE;
14699         END IF;
14700
14701         output.tcn := eg_tcn;
14702         output.tcn_source := eg_tcn_source;
14703         RETURN NEXT output;
14704
14705     END IF;
14706
14707     -- 022 a
14708     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14709     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14710
14711         eg_tcn_source := 'ISSN';
14712
14713         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14714
14715         IF NOT FOUND THEN
14716             output.used := FALSE;
14717         ELSE
14718             output.used := TRUE;
14719         END IF;
14720
14721         output.tcn := eg_tcn;
14722         output.tcn_source := eg_tcn_source;
14723         RETURN NEXT output;
14724
14725     END IF;
14726
14727     -- 010 a
14728     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14729     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14730
14731         eg_tcn_source := 'LCCN';
14732
14733         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14734
14735         IF NOT FOUND THEN
14736             output.used := FALSE;
14737         ELSE
14738             output.used := TRUE;
14739         END IF;
14740
14741         output.tcn := eg_tcn;
14742         output.tcn_source := eg_tcn_source;
14743         RETURN NEXT output;
14744
14745     END IF;
14746
14747     -- 035 a
14748     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
14749     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14750
14751         eg_tcn_source := 'System Legacy';
14752
14753         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14754
14755         IF NOT FOUND THEN
14756             output.used := FALSE;
14757         ELSE
14758             output.used := TRUE;
14759         END IF;
14760
14761         output.tcn := eg_tcn;
14762         output.tcn_source := eg_tcn_source;
14763         RETURN NEXT output;
14764
14765     END IF;
14766
14767     RETURN;
14768 END;
14769 $_$ LANGUAGE PLPGSQL;
14770
14771 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14772
14773 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);
14774
14775 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
14776
14777 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14778 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14779 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14780 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14781 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14782
14783 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14784 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14785 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14786 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14787
14788 ALTER TABLE metabib.series_field_entry
14789         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14790                 REFERENCES biblio.record_entry (id)
14791                 ON DELETE CASCADE
14792                 DEFERRABLE INITIALLY DEFERRED;
14793
14794 ALTER TABLE metabib.series_field_entry
14795         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14796                 REFERENCES config.metabib_field (id)
14797                 ON DELETE CASCADE
14798                 DEFERRABLE INITIALLY DEFERRED;
14799
14800 CREATE TABLE acq.claim_policy_action (
14801         id              SERIAL       PRIMARY KEY,
14802         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14803                                  ON DELETE CASCADE
14804                                      DEFERRABLE INITIALLY DEFERRED,
14805         action_interval INTERVAL     NOT NULL,
14806         action          INT          NOT NULL REFERENCES acq.claim_event_type
14807                                      DEFERRABLE INITIALLY DEFERRED,
14808         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14809 );
14810
14811 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14812 DECLARE
14813     value       TEXT;
14814     atype       TEXT;
14815     prov        INT;
14816     pos         INT;
14817     adef        RECORD;
14818     xpath_string    TEXT;
14819 BEGIN
14820     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14821  
14822         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14823  
14824         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14825             IF (atype = 'lineitem_provider_attr_definition') THEN
14826                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14827                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14828             END IF;
14829  
14830             IF (atype = 'lineitem_provider_attr_definition') THEN
14831                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14832             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14833                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14834             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14835                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14836             END IF;
14837  
14838             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14839  
14840             pos := 1;
14841  
14842             LOOP
14843                 SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14844  
14845                 IF (value IS NOT NULL AND value <> '') THEN
14846                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14847                         VALUES (NEW.id, adef.id, atype, adef.code, value);
14848                 ELSE
14849                     EXIT;
14850                 END IF;
14851  
14852                 pos := pos + 1;
14853             END LOOP;
14854  
14855         END IF;
14856  
14857     END LOOP;
14858  
14859     RETURN NULL;
14860 END;
14861 $function$ LANGUAGE PLPGSQL;
14862
14863 UPDATE config.metabib_field SET label = name;
14864 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14865
14866 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
14867          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
14868
14869 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
14870
14871 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
14872
14873 CREATE TABLE config.metabib_search_alias (
14874     alias       TEXT    PRIMARY KEY,
14875     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
14876     field       INT     REFERENCES config.metabib_field (id)
14877 );
14878
14879 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
14880 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
14881 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
14882 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
14883 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
14884 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
14885 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
14886 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
14887
14888 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
14889 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
14890 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
14891 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
14892 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
14893 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
14894 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
14895 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
14896 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
14897 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
14898 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
14899 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
14900 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
14901
14902 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
14903 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
14904 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
14905 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
14906 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
14907 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
14908 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
14909 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
14910
14911 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
14912 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
14913 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
14914 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
14915 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
14916 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
14917
14918 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
14919 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
14920 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
14921
14922 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
14923 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;
14924 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;
14925 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;
14926 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;
14927
14928 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
14929 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
14930 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
14931 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
14932
14933 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
14934 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
14935 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
14936 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
14937 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
14938 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
14939 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
14940 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
14941 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
14942 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
14943 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
14944 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
14945 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
14946 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
14947
14948 CREATE TABLE asset.opac_visible_copies (
14949   id        BIGINT primary key, -- copy id
14950   record    BIGINT,
14951   circ_lib  INTEGER
14952 );
14953 COMMENT ON TABLE asset.opac_visible_copies IS $$
14954 Materialized view of copies that are visible in the OPAC, used by
14955 search.query_parser_fts() to speed up OPAC visibility checks on large
14956 databases.  Contents are maintained by a set of triggers.
14957 $$;
14958 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
14959
14960 CREATE OR REPLACE FUNCTION search.query_parser_fts (
14961
14962     param_search_ou INT,
14963     param_depth     INT,
14964     param_query     TEXT,
14965     param_statuses  INT[],
14966     param_locations INT[],
14967     param_offset    INT,
14968     param_check     INT,
14969     param_limit     INT,
14970     metarecord      BOOL,
14971     staff           BOOL
14972  
14973 ) RETURNS SETOF search.search_result AS $func$
14974 DECLARE
14975
14976     current_res         search.search_result%ROWTYPE;
14977     search_org_list     INT[];
14978
14979     check_limit         INT;
14980     core_limit          INT;
14981     core_offset         INT;
14982     tmp_int             INT;
14983
14984     core_result         RECORD;
14985     core_cursor         REFCURSOR;
14986     core_rel_query      TEXT;
14987
14988     total_count         INT := 0;
14989     check_count         INT := 0;
14990     deleted_count       INT := 0;
14991     visible_count       INT := 0;
14992     excluded_count      INT := 0;
14993
14994 BEGIN
14995
14996     check_limit := COALESCE( param_check, 1000 );
14997     core_limit  := COALESCE( param_limit, 25000 );
14998     core_offset := COALESCE( param_offset, 0 );
14999
15000     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
15001
15002     IF param_search_ou > 0 THEN
15003         IF param_depth IS NOT NULL THEN
15004             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
15005         ELSE
15006             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
15007         END IF;
15008     ELSIF param_search_ou < 0 THEN
15009         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
15010     ELSIF param_search_ou = 0 THEN
15011         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
15012     END IF;
15013
15014     OPEN core_cursor FOR EXECUTE param_query;
15015
15016     LOOP
15017
15018         FETCH core_cursor INTO core_result;
15019         EXIT WHEN NOT FOUND;
15020         EXIT WHEN total_count >= core_limit;
15021
15022         total_count := total_count + 1;
15023
15024         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
15025
15026         check_count := check_count + 1;
15027
15028         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15029         IF NOT FOUND THEN
15030             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
15031             deleted_count := deleted_count + 1;
15032             CONTINUE;
15033         END IF;
15034
15035         PERFORM 1
15036           FROM  biblio.record_entry b
15037                 JOIN config.bib_source s ON (b.source = s.id)
15038           WHERE s.transcendant
15039                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15040
15041         IF FOUND THEN
15042             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
15043             visible_count := visible_count + 1;
15044
15045             current_res.id = core_result.id;
15046             current_res.rel = core_result.rel;
15047
15048             tmp_int := 1;
15049             IF metarecord THEN
15050                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15051             END IF;
15052
15053             IF tmp_int = 1 THEN
15054                 current_res.record = core_result.records[1];
15055             ELSE
15056                 current_res.record = NULL;
15057             END IF;
15058
15059             RETURN NEXT current_res;
15060
15061             CONTINUE;
15062         END IF;
15063
15064         PERFORM 1
15065           FROM  asset.call_number cn
15066                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
15067                 JOIN asset.uri uri ON (map.uri = uri.id)
15068           WHERE NOT cn.deleted
15069                 AND cn.label = '##URI##'
15070                 AND uri.active
15071                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
15072                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15073                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15074           LIMIT 1;
15075
15076         IF FOUND THEN
15077             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
15078             visible_count := visible_count + 1;
15079
15080             current_res.id = core_result.id;
15081             current_res.rel = core_result.rel;
15082
15083             tmp_int := 1;
15084             IF metarecord THEN
15085                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15086             END IF;
15087
15088             IF tmp_int = 1 THEN
15089                 current_res.record = core_result.records[1];
15090             ELSE
15091                 current_res.record = NULL;
15092             END IF;
15093
15094             RETURN NEXT current_res;
15095
15096             CONTINUE;
15097         END IF;
15098
15099         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
15100
15101             PERFORM 1
15102               FROM  asset.call_number cn
15103                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15104               WHERE NOT cn.deleted
15105                     AND NOT cp.deleted
15106                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
15107                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15108                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15109               LIMIT 1;
15110
15111             IF NOT FOUND THEN
15112                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
15113                 excluded_count := excluded_count + 1;
15114                 CONTINUE;
15115             END IF;
15116
15117         END IF;
15118
15119         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
15120
15121             PERFORM 1
15122               FROM  asset.call_number cn
15123                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15124               WHERE NOT cn.deleted
15125                     AND NOT cp.deleted
15126                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
15127                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15128                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15129               LIMIT 1;
15130
15131             IF NOT FOUND THEN
15132                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
15133                 excluded_count := excluded_count + 1;
15134                 CONTINUE;
15135             END IF;
15136
15137         END IF;
15138
15139         IF staff IS NULL OR NOT staff THEN
15140
15141             PERFORM 1
15142               FROM  asset.opac_visible_copies
15143               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15144                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15145               LIMIT 1;
15146
15147             IF NOT FOUND THEN
15148                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15149                 excluded_count := excluded_count + 1;
15150                 CONTINUE;
15151             END IF;
15152
15153         ELSE
15154
15155             PERFORM 1
15156               FROM  asset.call_number cn
15157                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15158                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15159               WHERE NOT cn.deleted
15160                     AND NOT cp.deleted
15161                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15162                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15163               LIMIT 1;
15164
15165             IF NOT FOUND THEN
15166
15167                 PERFORM 1
15168                   FROM  asset.call_number cn
15169                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15170                   LIMIT 1;
15171
15172                 IF FOUND THEN
15173                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15174                     excluded_count := excluded_count + 1;
15175                     CONTINUE;
15176                 END IF;
15177
15178             END IF;
15179
15180         END IF;
15181
15182         visible_count := visible_count + 1;
15183
15184         current_res.id = core_result.id;
15185         current_res.rel = core_result.rel;
15186
15187         tmp_int := 1;
15188         IF metarecord THEN
15189             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15190         END IF;
15191
15192         IF tmp_int = 1 THEN
15193             current_res.record = core_result.records[1];
15194         ELSE
15195             current_res.record = NULL;
15196         END IF;
15197
15198         RETURN NEXT current_res;
15199
15200         IF visible_count % 1000 = 0 THEN
15201             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15202         END IF;
15203
15204     END LOOP;
15205
15206     current_res.id = NULL;
15207     current_res.rel = NULL;
15208     current_res.record = NULL;
15209     current_res.total = total_count;
15210     current_res.checked = check_count;
15211     current_res.deleted = deleted_count;
15212     current_res.visible = visible_count;
15213     current_res.excluded = excluded_count;
15214
15215     CLOSE core_cursor;
15216
15217     RETURN NEXT current_res;
15218
15219 END;
15220 $func$ LANGUAGE PLPGSQL;
15221
15222 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15223 ALTER TABLE biblio.record_entry
15224          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15225          REFERENCES actor.org_unit (id)
15226          DEFERRABLE INITIALLY DEFERRED;
15227
15228 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15229
15230 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15231 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15232
15233 DROP VIEW auditor.biblio_record_entry_lifecycle;
15234
15235 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15236
15237 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15238         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15239 $$ LANGUAGE SQL STRICT IMMUTABLE;
15240
15241 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15242     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15243 $$ LANGUAGE SQL STRICT IMMUTABLE;
15244
15245 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15246     return lc(shift);
15247 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15248
15249 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15250     return uc(shift);
15251 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15252
15253 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15254     use Unicode::Normalize;
15255
15256     my $x = NFD(shift);
15257     $x =~ s/\pM+//go;
15258     return $x;
15259
15260 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15261
15262 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15263     use Unicode::Normalize;
15264
15265     my $x = NFC(shift);
15266     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15267     return $x;
15268
15269 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15270
15271 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15272 DECLARE
15273     setting RECORD;
15274     cur_org INT;
15275 BEGIN
15276     cur_org := org_id;
15277     LOOP
15278         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15279         IF FOUND THEN
15280             RETURN NEXT setting;
15281         END IF;
15282         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15283         EXIT WHEN cur_org IS NULL;
15284     END LOOP;
15285     RETURN;
15286 END;
15287 $$ LANGUAGE plpgsql STABLE;
15288
15289 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15290 DECLARE
15291     counter INT;
15292     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15293 BEGIN
15294
15295     SELECT  COUNT(*) INTO counter
15296       FROM  oils_xpath_table(
15297                 'id',
15298                 'marc',
15299                 'acq.lineitem',
15300                 '//*[@tag="' || tag || '"]',
15301                 'id=' || lineitem
15302             ) as t(i int,c text);
15303
15304     FOR i IN 1 .. counter LOOP
15305         FOR lida IN
15306             SELECT  *
15307               FROM  (   SELECT  id,i,t,v
15308                           FROM  oils_xpath_table(
15309                                     'id',
15310                                     'marc',
15311                                     'acq.lineitem',
15312                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15313                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15314                                     'id=' || lineitem
15315                                 ) as t(id int,t text,v text)
15316                     )x
15317         LOOP
15318             RETURN NEXT lida;
15319         END LOOP;
15320     END LOOP;
15321
15322     RETURN;
15323 END;
15324 $$ LANGUAGE PLPGSQL;
15325
15326 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15327 DECLARE
15328     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15329     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15330     result      config.i18n_core%ROWTYPE;
15331     fallback    TEXT;
15332     keyfield    TEXT := keyclass || '.' || keycol;
15333 BEGIN
15334
15335     -- Try the full locale
15336     SELECT  * INTO result
15337       FROM  config.i18n_core
15338       WHERE fq_field = keyfield
15339             AND identity_value = keyvalue
15340             AND translation = locale;
15341
15342     -- Try just the language
15343     IF NOT FOUND THEN
15344         SELECT  * INTO result
15345           FROM  config.i18n_core
15346           WHERE fq_field = keyfield
15347                 AND identity_value = keyvalue
15348                 AND translation = language;
15349     END IF;
15350
15351     -- Fall back to the string we passed in in the first place
15352     IF NOT FOUND THEN
15353     EXECUTE
15354             'SELECT ' ||
15355                 keycol ||
15356             ' FROM ' || keytable ||
15357             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15358                 INTO fallback;
15359         RETURN fallback;
15360     END IF;
15361
15362     RETURN result.string;
15363 END;
15364 $func$ LANGUAGE PLPGSQL STABLE;
15365
15366 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15367
15368 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15369
15370 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15371
15372 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15373     3, 1, 'delivered_but_lost',
15374     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15375
15376 CREATE TABLE config.global_flag (
15377     label   TEXT    NOT NULL
15378 ) INHERITS (config.internal_flag);
15379 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15380
15381 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
15382     VALUES (
15383         'cat.bib.use_id_for_tcn',
15384         oils_i18n_gettext(
15385             'cat.bib.use_id_for_tcn',
15386             'Cat: Use Internal ID for TCN Value',
15387             'cgf', 
15388             'label'
15389         )
15390     );
15391
15392 -- resolves performance issue noted by EG Indiana
15393
15394 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15395
15396 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15397
15398 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15399     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15400 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15401     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15402 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15403     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15404 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15405     (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 );
15406 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15407     (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 );
15408 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15409     (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 );
15410 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15411     (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 );
15412 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15413     (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 );
15414 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15415     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15416
15417 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15418  
15419
15420 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15421
15422 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15423 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15424 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15425 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15426 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15427 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15428
15429 CREATE TABLE metabib.identifier_field_entry (
15430         id              BIGSERIAL       PRIMARY KEY,
15431         source          BIGINT          NOT NULL,
15432         field           INT             NOT NULL,
15433         value           TEXT            NOT NULL,
15434         index_vector    tsvector        NOT NULL
15435 );
15436 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15437         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15438         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15439
15440 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15441 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15442     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15443 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15444
15445 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15446     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15447 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15448     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15449
15450 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15451     use Business::ISBN;
15452     use strict;
15453     use warnings;
15454
15455     # For each ISBN found in a single string containing a set of ISBNs:
15456     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15457     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15458
15459     my $input = shift;
15460     my $output = '';
15461
15462     foreach my $word (split(/\s/, $input)) {
15463         my $isbn = Business::ISBN->new($word);
15464
15465         # First check the checksum; if it is not valid, fix it and add the original
15466         # bad-checksum ISBN to the output
15467         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15468             $output .= $isbn->isbn() . " ";
15469             $isbn->fix_checksum();
15470         }
15471
15472         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15473         # and add the normalized original ISBN to the output
15474         if ($isbn && $isbn->is_valid()) {
15475             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15476             $output .= $isbn->isbn . " ";
15477
15478             # If we successfully converted the ISBN to its counterpart, add the
15479             # converted ISBN to the output as well
15480             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15481         }
15482     }
15483     return $output if $output;
15484
15485     # If there were no valid ISBNs, just return the raw input
15486     return $input;
15487 $func$ LANGUAGE PLPERLU;
15488
15489 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15490 /*
15491  * Copyright (C) 2010 Merrimack Valley Library Consortium
15492  * Jason Stephenson <jstephenson@mvlc.org>
15493  * Copyright (C) 2010 Laurentian University
15494  * Dan Scott <dscott@laurentian.ca>
15495  *
15496  * The translate_isbn1013 function takes an input ISBN and returns the
15497  * following in a single space-delimited string if the input ISBN is valid:
15498  *   - The normalized input ISBN (hyphens stripped)
15499  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15500  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15501  */
15502 $$;
15503
15504 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15505 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15506 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15507 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15508 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15509 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15510
15511 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15512         'ISBN 10/13 conversion',
15513         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15514         'translate_isbn1013',
15515         0
15516 );
15517
15518 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15519         'Replace',
15520         'Replace all occurences of first parameter in the string with the second parameter.',
15521         'replace',
15522         2
15523 );
15524
15525 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15526     SELECT  m.id, i.id, 1
15527       FROM  config.metabib_field m,
15528             config.index_normalizer i
15529       WHERE i.func IN ('first_word')
15530             AND m.id IN (18);
15531
15532 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15533     SELECT  m.id, i.id, 2
15534       FROM  config.metabib_field m,
15535             config.index_normalizer i
15536       WHERE i.func IN ('translate_isbn1013')
15537             AND m.id IN (18);
15538
15539 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15540     SELECT  m.id, i.id, $$['-','']$$
15541       FROM  config.metabib_field m,
15542             config.index_normalizer i
15543       WHERE i.func IN ('replace')
15544             AND m.id IN (19);
15545
15546 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15547     SELECT  m.id, i.id, $$[' ','']$$
15548       FROM  config.metabib_field m,
15549             config.index_normalizer i
15550       WHERE i.func IN ('replace')
15551             AND m.id IN (19);
15552
15553 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15554
15555 UPDATE  config.metabib_field_index_norm_map
15556   SET   params = REPLACE(params,E'\'','"')
15557   WHERE params IS NOT NULL AND params <> '';
15558
15559 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15560
15561 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15562
15563 ALTER TABLE config.circ_modifier
15564         ADD COLUMN avg_wait_time INTERVAL;
15565
15566 --CREATE TABLE actor.usr_password_reset (
15567 --  id SERIAL PRIMARY KEY,
15568 --  uuid TEXT NOT NULL, 
15569 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15570 --  request_time TIMESTAMP NOT NULL DEFAULT NOW(), 
15571 --  has_been_reset BOOL NOT NULL DEFAULT false
15572 --);
15573 --COMMENT ON TABLE actor.usr_password_reset IS $$
15574 --/*
15575 -- * Copyright (C) 2010 Laurentian University
15576 -- * Dan Scott <dscott@laurentian.ca>
15577 -- *
15578 -- * Self-serve password reset requests
15579 -- *
15580 -- * ****
15581 -- *
15582 -- * This program is free software; you can redistribute it and/or
15583 -- * modify it under the terms of the GNU General Public License
15584 -- * as published by the Free Software Foundation; either version 2
15585 -- * of the License, or (at your option) any later version.
15586 -- *
15587 -- * This program is distributed in the hope that it will be useful,
15588 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15589 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15590 -- * GNU General Public License for more details.
15591 -- */
15592 --$$;
15593 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15594 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15595 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15596 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15597
15598 -- Use the identifier search class tsconfig
15599 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15600 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15601     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15602     FOR EACH ROW
15603     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15604
15605 INSERT INTO config.global_flag (name,label,enabled)
15606     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15607 INSERT INTO config.global_flag (name,label,enabled)
15608     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15609
15610 -- turn a JSON scalar into an SQL TEXT value
15611 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15612     use JSON::XS;                    
15613     my $json = shift();
15614     my $txt;
15615     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15616     return undef if ($@);
15617     return $txt
15618 $f$ LANGUAGE PLPERLU;
15619
15620 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15621 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15622 DECLARE
15623     c               action.circulation%ROWTYPE;
15624     view_age        INTERVAL;
15625     usr_view_age    actor.usr_setting%ROWTYPE;
15626     usr_view_start  actor.usr_setting%ROWTYPE;
15627 BEGIN
15628     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15629     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15630
15631     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15632         -- User opted in and supplied a retention age
15633         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15634             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15635         ELSE
15636             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15637         END IF;
15638     ELSIF usr_view_start.value IS NOT NULL THEN
15639         -- User opted in
15640         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15641     ELSE
15642         -- User did not opt in
15643         RETURN;
15644     END IF;
15645
15646     FOR c IN
15647         SELECT  *
15648           FROM  action.circulation
15649           WHERE usr = usr_id
15650                 AND parent_circ IS NULL
15651                 AND xact_start > NOW() - view_age
15652           ORDER BY xact_start
15653     LOOP
15654         RETURN NEXT c;
15655     END LOOP;
15656
15657     RETURN;
15658 END;
15659 $func$ LANGUAGE PLPGSQL;
15660
15661 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15662 DECLARE
15663     usr_keep_age    actor.usr_setting%ROWTYPE;
15664     usr_keep_start  actor.usr_setting%ROWTYPE;
15665     org_keep_age    INTERVAL;
15666     org_keep_count  INT;
15667
15668     keep_age        INTERVAL;
15669
15670     target_acp      RECORD;
15671     circ_chain_head action.circulation%ROWTYPE;
15672     circ_chain_tail action.circulation%ROWTYPE;
15673
15674     purge_position  INT;
15675     count_purged    INT;
15676 BEGIN
15677
15678     count_purged := 0;
15679
15680     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15681
15682     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15683     IF org_keep_count IS NULL THEN
15684         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15685     END IF;
15686
15687     -- First, find copies with more than keep_count non-renewal circs
15688     FOR target_acp IN
15689         SELECT  target_copy,
15690                 COUNT(*) AS total_real_circs
15691           FROM  action.circulation
15692           WHERE parent_circ IS NULL
15693                 AND xact_finish IS NOT NULL
15694           GROUP BY target_copy
15695           HAVING COUNT(*) > org_keep_count
15696     LOOP
15697         purge_position := 0;
15698         -- And, for those, select circs that are finished and older than keep_age
15699         FOR circ_chain_head IN
15700             SELECT  *
15701               FROM  action.circulation
15702               WHERE target_copy = target_acp.target_copy
15703                     AND parent_circ IS NULL
15704               ORDER BY xact_start
15705         LOOP
15706
15707             -- Stop once we've purged enough circs to hit org_keep_count
15708             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15709
15710             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15711             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15712
15713             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15714             usr_keep_age.value := NULL;
15715             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15716
15717             usr_keep_start.value := NULL;
15718             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15719
15720             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15721                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15722                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15723                 ELSE
15724                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15725                 END IF;
15726             ELSIF usr_keep_start.value IS NOT NULL THEN
15727                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15728             ELSE
15729                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15730             END IF;
15731
15732             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15733
15734             -- We've passed the purging tests, purge the circ chain starting at the end
15735             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15736             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15737                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15738                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15739             END LOOP;
15740
15741             count_purged := count_purged + 1;
15742             purge_position := purge_position + 1;
15743
15744         END LOOP;
15745     END LOOP;
15746 END;
15747 $func$ LANGUAGE PLPGSQL;
15748
15749 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15750 DECLARE
15751     h               action.hold_request%ROWTYPE;
15752     view_age        INTERVAL;
15753     view_count      INT;
15754     usr_view_count  actor.usr_setting%ROWTYPE;
15755     usr_view_age    actor.usr_setting%ROWTYPE;
15756     usr_view_start  actor.usr_setting%ROWTYPE;
15757 BEGIN
15758     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15759     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15760     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15761
15762     FOR h IN
15763         SELECT  *
15764           FROM  action.hold_request
15765           WHERE usr = usr_id
15766                 AND fulfillment_time IS NULL
15767                 AND cancel_time IS NULL
15768           ORDER BY request_time DESC
15769     LOOP
15770         RETURN NEXT h;
15771     END LOOP;
15772
15773     IF usr_view_start.value IS NULL THEN
15774         RETURN;
15775     END IF;
15776
15777     IF usr_view_age.value IS NOT NULL THEN
15778         -- User opted in and supplied a retention age
15779         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15780             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15781         ELSE
15782             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15783         END IF;
15784     ELSE
15785         -- User opted in
15786         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15787     END IF;
15788
15789     IF usr_view_count.value IS NOT NULL THEN
15790         view_count := oils_json_to_text(usr_view_count.value)::INT;
15791     ELSE
15792         view_count := 1000;
15793     END IF;
15794
15795     -- show some fulfilled/canceled holds
15796     FOR h IN
15797         SELECT  *
15798           FROM  action.hold_request
15799           WHERE usr = usr_id
15800                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
15801                 AND request_time > NOW() - view_age
15802           ORDER BY request_time DESC
15803           LIMIT view_count
15804     LOOP
15805         RETURN NEXT h;
15806     END LOOP;
15807
15808     RETURN;
15809 END;
15810 $func$ LANGUAGE PLPGSQL;
15811
15812 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
15813
15814 DROP TABLE IF EXISTS serial.index_summary CASCADE;
15815
15816 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
15817
15818 DROP TABLE IF EXISTS serial.issuance CASCADE;
15819
15820 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
15821
15822 DROP TABLE IF EXISTS serial.subscription CASCADE;
15823
15824 CREATE TABLE asset.copy_template (
15825         id             SERIAL   PRIMARY KEY,
15826         owning_lib     INT      NOT NULL
15827                                 REFERENCES actor.org_unit (id)
15828                                 DEFERRABLE INITIALLY DEFERRED,
15829         creator        BIGINT   NOT NULL
15830                                 REFERENCES actor.usr (id)
15831                                 DEFERRABLE INITIALLY DEFERRED,
15832         editor         BIGINT   NOT NULL
15833                                 REFERENCES actor.usr (id)
15834                                 DEFERRABLE INITIALLY DEFERRED,
15835         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15836         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15837         name           TEXT     NOT NULL,
15838         -- columns above this point are attributes of the template itself
15839         -- columns after this point are attributes of the copy this template modifies/creates
15840         circ_lib       INT      REFERENCES actor.org_unit (id)
15841                                 DEFERRABLE INITIALLY DEFERRED,
15842         status         INT      REFERENCES config.copy_status (id)
15843                                 DEFERRABLE INITIALLY DEFERRED,
15844         location       INT      REFERENCES asset.copy_location (id)
15845                                 DEFERRABLE INITIALLY DEFERRED,
15846         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
15847                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
15848         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
15849                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
15850         age_protect    INT,
15851         circulate      BOOL,
15852         deposit        BOOL,
15853         ref            BOOL,
15854         holdable       BOOL,
15855         deposit_amount NUMERIC(6,2),
15856         price          NUMERIC(8,2),
15857         circ_modifier  TEXT,
15858         circ_as_type   TEXT,
15859         alert_message  TEXT,
15860         opac_visible   BOOL,
15861         floating       BOOL,
15862         mint_condition BOOL
15863 );
15864
15865 CREATE TABLE serial.subscription (
15866         id                     SERIAL       PRIMARY KEY,
15867         owning_lib             INT          NOT NULL DEFAULT 1
15868                                             REFERENCES actor.org_unit (id)
15869                                             ON DELETE SET NULL
15870                                             DEFERRABLE INITIALLY DEFERRED,
15871         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
15872         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
15873         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
15874                                             ON DELETE SET NULL
15875                                             DEFERRABLE INITIALLY DEFERRED,
15876         expected_date_offset   INTERVAL
15877         -- acquisitions/business-side tables link to here
15878 );
15879 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
15880 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
15881
15882 --at least one distribution per org_unit holding issues
15883 CREATE TABLE serial.distribution (
15884         id                    SERIAL  PRIMARY KEY,
15885         record_entry          BIGINT  REFERENCES serial.record_entry (id)
15886                                       ON DELETE SET NULL
15887                                       DEFERRABLE INITIALLY DEFERRED,
15888         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
15889                                           summary_method IS NULL
15890                                           OR summary_method IN ( 'add_to_sre',
15891                                           'merge_with_sre', 'use_sre_only',
15892                                           'use_sdist_only')),
15893         subscription          INT     NOT NULL
15894                                       REFERENCES serial.subscription (id)
15895                                                                   ON DELETE CASCADE
15896                                                                   DEFERRABLE INITIALLY DEFERRED,
15897         holding_lib           INT     NOT NULL
15898                                       REFERENCES actor.org_unit (id)
15899                                                                   DEFERRABLE INITIALLY DEFERRED,
15900         label                 TEXT    NOT NULL,
15901         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
15902                                       DEFERRABLE INITIALLY DEFERRED,
15903         receive_unit_template INT     REFERENCES asset.copy_template (id)
15904                                       DEFERRABLE INITIALLY DEFERRED,
15905         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
15906                                       DEFERRABLE INITIALLY DEFERRED,
15907         bind_unit_template    INT     REFERENCES asset.copy_template (id)
15908                                       DEFERRABLE INITIALLY DEFERRED,
15909         unit_label_prefix     TEXT,
15910         unit_label_suffix     TEXT
15911 );
15912 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
15913 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
15914
15915 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
15916
15917 CREATE TABLE serial.stream (
15918         id              SERIAL  PRIMARY KEY,
15919         distribution    INT     NOT NULL
15920                                 REFERENCES serial.distribution (id)
15921                                 ON DELETE CASCADE
15922                                 DEFERRABLE INITIALLY DEFERRED,
15923         routing_label   TEXT
15924 );
15925 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
15926
15927 CREATE UNIQUE INDEX label_once_per_dist
15928         ON serial.stream (distribution, routing_label)
15929         WHERE routing_label IS NOT NULL;
15930
15931 CREATE TABLE serial.routing_list_user (
15932         id             SERIAL       PRIMARY KEY,
15933         stream         INT          NOT NULL
15934                                     REFERENCES serial.stream
15935                                     ON DELETE CASCADE
15936                                     DEFERRABLE INITIALLY DEFERRED,
15937         pos            INT          NOT NULL DEFAULT 1,
15938         reader         INT          REFERENCES actor.usr
15939                                     ON DELETE CASCADE
15940                                     DEFERRABLE INITIALLY DEFERRED,
15941         department     TEXT,
15942         note           TEXT,
15943         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
15944         CONSTRAINT reader_or_dept CHECK
15945         (
15946             -- Recipient is a person or a department, but not both
15947                 (reader IS NOT NULL AND department IS NULL) OR
15948                 (reader IS NULL AND department IS NOT NULL)
15949         )
15950 );
15951 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
15952 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
15953
15954 CREATE TABLE serial.caption_and_pattern (
15955         id           SERIAL       PRIMARY KEY,
15956         subscription INT          NOT NULL REFERENCES serial.subscription (id)
15957                                   ON DELETE CASCADE
15958                                   DEFERRABLE INITIALLY DEFERRED,
15959         type         TEXT         NOT NULL
15960                                   CONSTRAINT cap_type CHECK ( type in
15961                                   ( 'basic', 'supplement', 'index' )),
15962         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
15963         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
15964         end_date     TIMESTAMP WITH TIME ZONE,
15965         active       BOOL         NOT NULL DEFAULT FALSE,
15966         pattern_code TEXT         NOT NULL,       -- must contain JSON
15967         enum_1       TEXT,
15968         enum_2       TEXT,
15969         enum_3       TEXT,
15970         enum_4       TEXT,
15971         enum_5       TEXT,
15972         enum_6       TEXT,
15973         chron_1      TEXT,
15974         chron_2      TEXT,
15975         chron_3      TEXT,
15976         chron_4      TEXT,
15977         chron_5      TEXT
15978 );
15979 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
15980
15981 CREATE TABLE serial.issuance (
15982         id              SERIAL    PRIMARY KEY,
15983         creator         INT       NOT NULL
15984                                   REFERENCES actor.usr (id)
15985                                                           DEFERRABLE INITIALLY DEFERRED,
15986         editor          INT       NOT NULL
15987                                   REFERENCES actor.usr (id)
15988                                   DEFERRABLE INITIALLY DEFERRED,
15989         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15990         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15991         subscription    INT       NOT NULL
15992                                   REFERENCES serial.subscription (id)
15993                                   ON DELETE CASCADE
15994                                   DEFERRABLE INITIALLY DEFERRED,
15995         label           TEXT,
15996         date_published  TIMESTAMP WITH TIME ZONE,
15997         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
15998                               DEFERRABLE INITIALLY DEFERRED,
15999         holding_code    TEXT,
16000         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
16001                                   (
16002                                       holding_type IS NULL
16003                                       OR holding_type IN ('basic','supplement','index')
16004                                   ),
16005         holding_link_id INT
16006         -- TODO: add columns for separate enumeration/chronology values
16007 );
16008 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
16009 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
16010 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
16011
16012 CREATE TABLE serial.unit (
16013         label           TEXT,
16014         label_sort_key  TEXT,
16015         contents        TEXT    NOT NULL
16016 ) INHERITS (asset.copy);
16017 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
16018 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
16019 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
16020 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
16021 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
16022
16023 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
16024
16025 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
16026
16027 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16028
16029 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16030
16031 CREATE TABLE serial.item (
16032         id              SERIAL  PRIMARY KEY,
16033         creator         INT     NOT NULL
16034                                 REFERENCES actor.usr (id)
16035                                 DEFERRABLE INITIALLY DEFERRED,
16036         editor          INT     NOT NULL
16037                                 REFERENCES actor.usr (id)
16038                                 DEFERRABLE INITIALLY DEFERRED,
16039         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16040         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16041         issuance        INT     NOT NULL
16042                                 REFERENCES serial.issuance (id)
16043                                 ON DELETE CASCADE
16044                                 DEFERRABLE INITIALLY DEFERRED,
16045         stream          INT     NOT NULL
16046                                 REFERENCES serial.stream (id)
16047                                 ON DELETE CASCADE
16048                                 DEFERRABLE INITIALLY DEFERRED,
16049         unit            INT     REFERENCES serial.unit (id)
16050                                 ON DELETE SET NULL
16051                                 DEFERRABLE INITIALLY DEFERRED,
16052         uri             INT     REFERENCES asset.uri (id)
16053                                 ON DELETE SET NULL
16054                                 DEFERRABLE INITIALLY DEFERRED,
16055         date_expected   TIMESTAMP WITH TIME ZONE,
16056         date_received   TIMESTAMP WITH TIME ZONE,
16057         status          TEXT    CONSTRAINT valid_status CHECK (
16058                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
16059                                'Expected', 'Not Held', 'Not Published', 'Received'))
16060                             DEFAULT 'Expected',
16061         shadowed        BOOL    NOT NULL DEFAULT FALSE
16062 );
16063 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
16064 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
16065 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
16066 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
16067 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
16068 CREATE INDEX serial_item_status_idx ON serial.item (status);
16069
16070 CREATE TABLE serial.item_note (
16071         id          SERIAL  PRIMARY KEY,
16072         item        INT     NOT NULL
16073                             REFERENCES serial.item (id)
16074                             ON DELETE CASCADE
16075                             DEFERRABLE INITIALLY DEFERRED,
16076         creator     INT     NOT NULL
16077                             REFERENCES actor.usr (id)
16078                             DEFERRABLE INITIALLY DEFERRED,
16079         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
16080         pub         BOOL    NOT NULL    DEFAULT FALSE,
16081         title       TEXT    NOT NULL,
16082         value       TEXT    NOT NULL
16083 );
16084 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
16085
16086 CREATE TABLE serial.basic_summary (
16087         id                  SERIAL  PRIMARY KEY,
16088         distribution        INT     NOT NULL
16089                                     REFERENCES serial.distribution (id)
16090                                     ON DELETE CASCADE
16091                                     DEFERRABLE INITIALLY DEFERRED,
16092         generated_coverage  TEXT    NOT NULL,
16093         textual_holdings    TEXT,
16094         show_generated      BOOL    NOT NULL DEFAULT TRUE
16095 );
16096 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
16097
16098 CREATE TABLE serial.supplement_summary (
16099         id                  SERIAL  PRIMARY KEY,
16100         distribution        INT     NOT NULL
16101                                     REFERENCES serial.distribution (id)
16102                                     ON DELETE CASCADE
16103                                     DEFERRABLE INITIALLY DEFERRED,
16104         generated_coverage  TEXT    NOT NULL,
16105         textual_holdings    TEXT,
16106         show_generated      BOOL    NOT NULL DEFAULT TRUE
16107 );
16108 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
16109
16110 CREATE TABLE serial.index_summary (
16111         id                  SERIAL  PRIMARY KEY,
16112         distribution        INT     NOT NULL
16113                                     REFERENCES serial.distribution (id)
16114                                     ON DELETE CASCADE
16115                                     DEFERRABLE INITIALLY DEFERRED,
16116         generated_coverage  TEXT    NOT NULL,
16117         textual_holdings    TEXT,
16118         show_generated      BOOL    NOT NULL DEFAULT TRUE
16119 );
16120 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
16121
16122 -- 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.
16123
16124 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
16125 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16126
16127 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
16128 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;
16129
16130 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
16131 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16132
16133 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
16134 RETURNS INTEGER AS $$
16135 BEGIN
16136         RETURN EXTRACT( EPOCH FROM interval_val );
16137 END;
16138 $$ LANGUAGE plpgsql;
16139
16140 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
16141 RETURNS INTEGER AS $$
16142 BEGIN
16143         RETURN config.interval_to_seconds( interval_string::INTERVAL );
16144 END;
16145 $$ LANGUAGE plpgsql;
16146
16147 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
16148     'temp',
16149     oils_i18n_gettext(
16150         'temp',
16151         'Temporary bucket which gets deleted after use.',
16152         'cbrebt',
16153         'label'
16154     )
16155 );
16156
16157 -- 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.
16158
16159 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
16160 BEGIN
16161
16162     IF xml_is_well_formed(NEW.marc) THEN
16163         RETURN NEW;
16164     ELSE
16165         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16166     END IF;
16167     
16168 END;
16169 $func$ LANGUAGE PLPGSQL;
16170
16171 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();
16172
16173 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();
16174
16175 ALTER TABLE serial.record_entry
16176         ALTER COLUMN marc DROP NOT NULL;
16177
16178 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16179 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16180 <xsl:stylesheet
16181     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16182     xmlns:marc="http://www.loc.gov/MARC21/slim"
16183     version="1.0">
16184 <!--
16185 Copyright (C) 2010  Equinox Software, Inc.
16186 Galen Charlton <gmc@esilibrary.cOM.
16187
16188 This program is free software; you can redistribute it and/or
16189 modify it under the terms of the GNU General Public License
16190 as published by the Free Software Foundation; either version 2
16191 of the License, or (at your option) any later version.
16192
16193 This program is distributed in the hope that it will be useful,
16194 but WITHOUT ANY WARRANTY; without even the implied warranty of
16195 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16196 GNU General Public License for more details.
16197
16198 marc21_expand_880.xsl - stylesheet used during indexing to
16199                         map alternative graphical representations
16200                         of MARC fields stored in 880 fields
16201                         to the corresponding tag name and value.
16202
16203 For example, if a MARC record for a Chinese book has
16204
16205 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16206 880.00 $6 245-01/$1 $a八十三年短篇小說選
16207
16208 this stylesheet will transform it to the equivalent of
16209
16210 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16211 245.00 $6 245-01/$1 $a八十三年短篇小說選
16212
16213 -->
16214     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16215
16216     <xsl:template match="@*|node()">
16217         <xsl:copy>
16218             <xsl:apply-templates select="@*|node()"/>
16219         </xsl:copy>
16220     </xsl:template>
16221
16222     <xsl:template match="//marc:datafield[@tag='880']">
16223         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16224             <marc:datafield>
16225                 <xsl:attribute name="tag">
16226                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16227                 </xsl:attribute>
16228                 <xsl:attribute name="ind1">
16229                     <xsl:value-of select="@ind1" />
16230                 </xsl:attribute>
16231                 <xsl:attribute name="ind2">
16232                     <xsl:value-of select="@ind2" />
16233                 </xsl:attribute>
16234                 <xsl:apply-templates />
16235             </marc:datafield>
16236         </xsl:if>
16237     </xsl:template>
16238     
16239 </xsl:stylesheet>$$);
16240
16241 -- Splitting the ingest trigger up into little bits
16242
16243 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16244     flag INTEGER PRIMARY KEY
16245 ) ON COMMIT DROP;
16246 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16247
16248 -- cause failure if either of the tables we want to drop have rows
16249 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16250 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16251
16252 DROP TABLE IF EXISTS asset.copy_transparency_map;
16253 DROP TABLE IF EXISTS asset.copy_transparency;
16254
16255 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16256
16257 -- We won't necessarily use all of these, but they are here for completeness.
16258 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16259 -- Values are the EDI code value + 1000
16260
16261 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16262 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16263 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16264 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16265 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16266 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16267 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16268 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16269 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16270 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16271 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16272 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16273 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16274 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16275 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16276 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16277 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16278 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16279 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16280 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16281 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16282 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16283 ('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).'),
16284 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16285 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16286 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16287 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16288 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16289 ('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.'),
16290 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16291 ('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.'),
16292 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16293 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16294 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16295 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16296 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16297 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16298 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16299 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16300 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16301 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16302 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16303 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16304 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16305 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16306 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16307 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16308 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16309 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16310 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16311 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16312 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16313 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16314 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16315 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16316 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16317 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16318 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16319 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16320 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16321 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16322 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16323 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16324 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16325 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16326 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16327 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16328 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16329 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16330 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16331 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16332 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16333 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16334 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16335 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16336 ('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).'),
16337 ('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).'),
16338 ('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).'),
16339 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16340 ('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).'),
16341 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16342 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16343 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16344 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16345 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16346 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16347 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16348 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16349 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16350 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16351 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16352 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16353 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16354 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16355 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16356 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16357 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16358 ('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.'),
16359 ('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.'),
16360 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16361 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16362 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16363 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16364 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16365 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16366 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16367 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16368 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16369 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16370 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16371 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16372 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16373 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16374 ('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.'),
16375 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16376 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16377
16378 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16379     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16380  
16381 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16382  
16383 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16384         'Remove Parenthesized Substring',
16385         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16386         'remove_paren_substring',
16387         0
16388 );
16389
16390 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16391         'Trim Surrounding Space',
16392         'Trim leading and trailing spaces from extracted text.',
16393         'btrim',
16394         0
16395 );
16396
16397 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16398     SELECT  m.id,
16399             i.id,
16400             -2
16401       FROM  config.metabib_field m,
16402             config.index_normalizer i
16403       WHERE i.func IN ('remove_paren_substring')
16404             AND m.id IN (26);
16405
16406 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16407     SELECT  m.id,
16408             i.id,
16409             -1
16410       FROM  config.metabib_field m,
16411             config.index_normalizer i
16412       WHERE i.func IN ('btrim')
16413             AND m.id IN (26);
16414
16415 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16416 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16417 DECLARE
16418     dyn_profile     vandelay.compile_profile%ROWTYPE;
16419     replace_rule    TEXT;
16420     tmp_marc        TEXT;
16421     trgt_marc        TEXT;
16422     tmpl_marc        TEXT;
16423     match_count     INT;
16424 BEGIN
16425
16426     IF target_marc IS NULL OR template_marc IS NULL THEN
16427         -- RAISE NOTICE 'no marc for target or template record';
16428         RETURN NULL;
16429     END IF;
16430
16431     dyn_profile := vandelay.compile_profile( template_marc );
16432
16433     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16434         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16435         RETURN NULL;
16436     END IF;
16437
16438     IF dyn_profile.replace_rule <> '' THEN
16439         trgt_marc = target_marc;
16440         tmpl_marc = template_marc;
16441         replace_rule = dyn_profile.replace_rule;
16442     ELSE
16443         tmp_marc = target_marc;
16444         trgt_marc = template_marc;
16445         tmpl_marc = tmp_marc;
16446         replace_rule = dyn_profile.preserve_rule;
16447     END IF;
16448
16449     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16450
16451 END;
16452 $$ LANGUAGE PLPGSQL;
16453
16454 -- Function to generate an ephemeral overlay template from an authority record
16455 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16456
16457     use MARC::Record;
16458     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16459
16460     my $xml = shift;
16461     my $r = MARC::Record->new_from_xml( $xml );
16462
16463     return undef unless ($r);
16464
16465     my $id = shift() || $r->subfield( '901' => 'c' );
16466     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16467     return undef unless ($id); # We need an ID!
16468
16469     my $tmpl = MARC::Record->new();
16470
16471     my @rule_fields;
16472     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16473
16474         my $tag = $field->tag;
16475         my $i1 = $field->indicator(1);
16476         my $i2 = $field->indicator(2);
16477         my $sf = join '', map { $_->[0] } $field->subfields;
16478         my @data = map { @$_ } $field->subfields;
16479
16480         my @replace_them;
16481
16482         # Map the authority field to bib fields it can control.
16483         if ($tag >= 100 and $tag <= 111) {       # names
16484             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16485         } elsif ($tag eq '130') {                # uniform title
16486             @replace_them = qw/130 240 440 730 830/;
16487         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16488             @replace_them = ($tag + 500);
16489         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16490             @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/;
16491         } else {
16492             next;
16493         }
16494
16495         # Dummy up the bib-side data
16496         $tmpl->append_fields(
16497             map {
16498                 MARC::Field->new( $_, $i1, $i2, @data )
16499             } @replace_them
16500         );
16501
16502         # Construct some 'replace' rules
16503         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16504     }
16505
16506     # Insert the replace rules into the template
16507     $tmpl->append_fields(
16508         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16509     );
16510
16511     $xml = $tmpl->as_xml_record;
16512     $xml =~ s/^<\?.+?\?>$//mo;
16513     $xml =~ s/\n//sgo;
16514     $xml =~ s/>\s+</></sgo;
16515
16516     return $xml;
16517
16518 $func$ LANGUAGE PLPERLU;
16519
16520 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16521     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16522 $func$ LANGUAGE SQL;
16523
16524 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16525     SELECT authority.generate_overlay_template( $1, NULL );
16526 $func$ LANGUAGE SQL;
16527
16528 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16529 DELETE FROM config.metabib_field WHERE id = 26;
16530
16531 -- Making this a global_flag (UI accessible) instead of an internal_flag
16532 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16533     VALUES (
16534         'ingest.disable_authority_linking',
16535         oils_i18n_gettext(
16536             'ingest.disable_authority_linking',
16537             'Authority Automation: Disable bib-authority link tracking',
16538             'cgf', 
16539             'label'
16540         )
16541     );
16542 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16543 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16544
16545 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16546     VALUES (
16547         'ingest.disable_authority_auto_update',
16548         oils_i18n_gettext(
16549             'ingest.disable_authority_auto_update',
16550             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16551             'cgf', 
16552             'label'
16553         )
16554     );
16555
16556 -- Enable automated ingest of authority records; just insert the row into
16557 -- authority.record_entry and authority.full_rec will automatically be populated
16558
16559 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16560     UPDATE  biblio.record_entry
16561       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16562       WHERE id = $2;
16563     SELECT $1;
16564 $func$ LANGUAGE SQL;
16565
16566 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16567     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16568 $func$ LANGUAGE SQL;
16569
16570 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16571
16572 use MARC::Record;
16573 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16574
16575 my $xml = shift;
16576 my $r = MARC::Record->new_from_xml( $xml );
16577
16578 return_next( { tag => 'LDR', value => $r->leader } );
16579
16580 for my $f ( $r->fields ) {
16581     if ($f->is_control_field) {
16582         return_next({ tag => $f->tag, value => $f->data });
16583     } else {
16584         for my $s ($f->subfields) {
16585             return_next({
16586                 tag      => $f->tag,
16587                 ind1     => $f->indicator(1),
16588                 ind2     => $f->indicator(2),
16589                 subfield => $s->[0],
16590                 value    => $s->[1]
16591             });
16592
16593         }
16594     }
16595 }
16596
16597 return undef;
16598
16599 $func$ LANGUAGE PLPERLU;
16600
16601 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16602 DECLARE
16603     auth    authority.record_entry%ROWTYPE;
16604     output    authority.full_rec%ROWTYPE;
16605     field    RECORD;
16606 BEGIN
16607     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16608
16609     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16610         output.record := rid;
16611         output.ind1 := field.ind1;
16612         output.ind2 := field.ind2;
16613         output.tag := field.tag;
16614         output.subfield := field.subfield;
16615         IF field.subfield IS NOT NULL THEN
16616             output.value := naco_normalize(field.value, field.subfield);
16617         ELSE
16618             output.value := field.value;
16619         END IF;
16620
16621         CONTINUE WHEN output.value IS NULL;
16622
16623         RETURN NEXT output;
16624     END LOOP;
16625 END;
16626 $func$ LANGUAGE PLPGSQL;
16627
16628 -- authority.rec_descriptor appears to be unused currently
16629 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16630 BEGIN
16631     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16632 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16633 --        SELECT  auth_id, ;
16634
16635     RETURN;
16636 END;
16637 $func$ LANGUAGE PLPGSQL;
16638
16639 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16640 BEGIN
16641     DELETE FROM authority.full_rec WHERE record = auth_id;
16642     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16643         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16644
16645     RETURN;
16646 END;
16647 $func$ LANGUAGE PLPGSQL;
16648
16649 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16650 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16651 BEGIN
16652
16653     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16654         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16655         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
16656           -- Should remove matching $0 from controlled fields at the same time?
16657         RETURN NEW; -- and we're done
16658     END IF;
16659
16660     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16661         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16662
16663         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16664             RETURN NEW;
16665         END IF;
16666     END IF;
16667
16668     -- Flatten and insert the afr data
16669     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16670     IF NOT FOUND THEN
16671         PERFORM authority.reingest_authority_full_rec(NEW.id);
16672 -- authority.rec_descriptor is not currently used
16673 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16674 --        IF NOT FOUND THEN
16675 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16676 --        END IF;
16677     END IF;
16678
16679     RETURN NEW;
16680 END;
16681 $func$ LANGUAGE PLPGSQL;
16682
16683 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 ();
16684
16685 -- Some records manage to get XML namespace declarations into each element,
16686 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16687 -- This broke the old maintain_901(), so we'll make the regex more robust
16688
16689 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16690 BEGIN
16691     -- Remove any existing 901 fields before we insert the authoritative one
16692     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16693     IF TG_TABLE_SCHEMA = 'biblio' THEN
16694         NEW.marc := REGEXP_REPLACE(
16695             NEW.marc,
16696             E'(</(?:[^:]*?:)?record>)',
16697             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16698                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16699                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16700                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16701                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16702                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16703                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16704              E'</datafield>\\1'
16705         );
16706     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16707         NEW.marc := REGEXP_REPLACE(
16708             NEW.marc,
16709             E'(</(?:[^:]*?:)?record>)',
16710             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16711                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16712                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16713              E'</datafield>\\1'
16714         );
16715     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16716         NEW.marc := REGEXP_REPLACE(
16717             NEW.marc,
16718             E'(</(?:[^:]*?:)?record>)',
16719             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16720                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16721                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16722                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16723                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16724              E'</datafield>\\1'
16725         );
16726     ELSE
16727         NEW.marc := REGEXP_REPLACE(
16728             NEW.marc,
16729             E'(</(?:[^:]*?:)?record>)',
16730             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16731                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16732                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16733              E'</datafield>\\1'
16734         );
16735     END IF;
16736
16737     RETURN NEW;
16738 END;
16739 $func$ LANGUAGE PLPGSQL;
16740
16741 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16742 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16743 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16744  
16745 -- In booking, elbow room defines:
16746 --  a) how far in the future you must make a reservation on a given item if
16747 --      that item will have to transit somewhere to fulfill the reservation.
16748 --  b) how soon a reservation must be starting for the reserved item to
16749 --      be op-captured by the checkin interface.
16750
16751 -- We don't want to clobber any default_elbow room at any level:
16752
16753 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16754 DECLARE
16755     existing    actor.org_unit_setting%ROWTYPE;
16756 BEGIN
16757     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16758     IF NOT FOUND THEN
16759         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16760             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16761             'circ.booking_reservation.default_elbow_room',
16762             '"1 day"'
16763         );
16764         RETURN 1;
16765     END IF;
16766     RETURN 0;
16767 END;
16768 $$ LANGUAGE plpgsql;
16769
16770 SELECT pg_temp.default_elbow();
16771
16772 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16773
16774 -- returns the distinct set of target copy IDs from a user's visible circulation history
16775 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16776     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16777 $$ LANGUAGE SQL;
16778
16779 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
16780 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
16781 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
16782 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
16783
16784 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
16785
16786 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
16787 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
16788
16789 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16790     VALUES (
16791         'cat.maintain_control_numbers',
16792         oils_i18n_gettext(
16793             'cat.maintain_control_numbers',
16794             'Cat: Maintain 001/003/035 according to the MARC21 specification',
16795             'cgf', 
16796             'label'
16797         )
16798     );
16799
16800 INSERT INTO config.global_flag (name, label, enabled)
16801     VALUES (
16802         'circ.holds.empty_issuance_ok',
16803         oils_i18n_gettext(
16804             'circ.holds.empty_issuance_ok',
16805             'Holds: Allow holds on empty issuances',
16806             'cgf',
16807             'label'
16808         ),
16809         TRUE
16810     );
16811
16812 INSERT INTO config.global_flag (name, label, enabled)
16813     VALUES (
16814         'circ.holds.usr_not_requestor',
16815         oils_i18n_gettext(
16816             'circ.holds.usr_not_requestor',
16817             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
16818             'cgf',
16819             'label'
16820         ),
16821         TRUE
16822     );
16823
16824 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
16825 use strict;
16826 use MARC::Record;
16827 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16828 use Encode;
16829 use Unicode::Normalize;
16830
16831 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
16832 my $schema = $_TD->{table_schema};
16833 my $rec_id = $_TD->{new}{id};
16834
16835 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
16836 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
16837 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
16838     return;
16839 }
16840
16841 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
16842 my $ou_cni = 'EVRGRN';
16843
16844 my $owner;
16845 if ($schema eq 'serial') {
16846     $owner = $_TD->{new}{owning_lib};
16847 } else {
16848     # are.owner and bre.owner can be null, so fall back to the consortial setting
16849     $owner = $_TD->{new}{owner} || 1;
16850 }
16851
16852 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
16853 if ($ous_rv->{processed}) {
16854     $ou_cni = $ous_rv->{rows}[0]->{value};
16855     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
16856 } else {
16857     # Fall back to the shortname of the OU if there was no OU setting
16858     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
16859     if ($ous_rv->{processed}) {
16860         $ou_cni = $ous_rv->{rows}[0]->{shortname};
16861     }
16862 }
16863
16864 my ($create, $munge) = (0, 0);
16865 my ($orig_001, $orig_003) = ('', '');
16866
16867 # Incoming MARC records may have multiple 001s or 003s, despite the spec
16868 my @control_ids = $record->field('003');
16869 my @scns = $record->field('035');
16870
16871 foreach my $id_field ('001', '003') {
16872     my $spec_value;
16873     my @controls = $record->field($id_field);
16874
16875     if ($id_field eq '001') {
16876         $spec_value = $rec_id;
16877     } else {
16878         $spec_value = $ou_cni;
16879     }
16880
16881     # Create the 001/003 if none exist
16882     if (scalar(@controls) == 0) {
16883         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
16884         $create = 1;
16885     } elsif (scalar(@controls) > 1) {
16886         # Do we already have the right 001/003 value in the existing set?
16887         unless (grep $_->data() eq $spec_value, @controls) {
16888             $munge = 1;
16889         }
16890
16891         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
16892         foreach my $control (@controls) {
16893             unless ($control->data() eq $spec_value) {
16894                 $record->delete_field($control);
16895             }
16896         }
16897     } else {
16898         # Only one field; check to see if we need to munge it
16899         unless (grep $_->data() eq $spec_value, @controls) {
16900             $munge = 1;
16901         }
16902     }
16903 }
16904
16905 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
16906 if ($munge) {
16907     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
16908
16909     # Do not create duplicate 035 fields
16910     unless (grep $_->subfield('a') eq $scn, @scns) {
16911         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
16912     }
16913 }
16914
16915 # Set the 001/003 and update the MARC
16916 if ($create or $munge) {
16917     $record->field('001')->data($rec_id);
16918     $record->field('003')->data($ou_cni);
16919
16920     my $xml = $record->as_xml_record();
16921     $xml =~ s/\n//sgo;
16922     $xml =~ s/^<\?xml.+\?\s*>//go;
16923     $xml =~ s/>\s+</></go;
16924     $xml =~ s/\p{Cc}//go;
16925
16926     # Embed a version of OpenILS::Application::AppUtils->entityize()
16927     # to avoid having to set PERL5LIB for PostgreSQL as well
16928
16929     # If we are going to convert non-ASCII characters to XML entities,
16930     # we had better be dealing with a UTF8 string to begin with
16931     $xml = decode_utf8($xml);
16932
16933     $xml = NFC($xml);
16934
16935     # Convert raw ampersands to entities
16936     $xml =~ s/&(?!\S+;)/&amp;/gso;
16937
16938     # Convert Unicode characters to entities
16939     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
16940
16941     $xml =~ s/[\x00-\x1f]//go;
16942     $_TD->{new}{marc} = $xml;
16943
16944     return "MODIFY";
16945 }
16946
16947 return;
16948 $func$ LANGUAGE PLPERLU;
16949
16950 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16951 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16952 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16953
16954 INSERT INTO metabib.facet_entry (source, field, value)
16955     SELECT source, field, value FROM (
16956         SELECT * FROM metabib.author_field_entry
16957             UNION ALL
16958         SELECT * FROM metabib.keyword_field_entry
16959             UNION ALL
16960         SELECT * FROM metabib.identifier_field_entry
16961             UNION ALL
16962         SELECT * FROM metabib.title_field_entry
16963             UNION ALL
16964         SELECT * FROM metabib.subject_field_entry
16965             UNION ALL
16966         SELECT * FROM metabib.series_field_entry
16967         )x
16968     WHERE x.index_vector = '';
16969         
16970 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
16971 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
16972 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
16973 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
16974 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
16975 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
16976
16977 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
16978 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
16979 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
16980
16981 -- copy OPAC visibility materialized view
16982 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
16983
16984     TRUNCATE TABLE asset.opac_visible_copies;
16985
16986     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16987     SELECT  cp.id, cp.circ_lib, cn.record
16988     FROM  asset.copy cp
16989         JOIN asset.call_number cn ON (cn.id = cp.call_number)
16990         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
16991         JOIN asset.copy_location cl ON (cp.location = cl.id)
16992         JOIN config.copy_status cs ON (cp.status = cs.id)
16993         JOIN biblio.record_entry b ON (cn.record = b.id)
16994     WHERE NOT cp.deleted
16995         AND NOT cn.deleted
16996         AND NOT b.deleted
16997         AND cs.opac_visible
16998         AND cl.opac_visible
16999         AND cp.opac_visible
17000         AND a.opac_visible;
17001
17002 $$ LANGUAGE SQL;
17003 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
17004 Rebuild the copy OPAC visibility cache.  Useful during migrations.
17005 $$;
17006
17007 -- and actually populate the table
17008 SELECT asset.refresh_opac_visible_copies_mat_view();
17009
17010 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
17011 DECLARE
17012     add_query       TEXT;
17013     remove_query    TEXT;
17014     do_add          BOOLEAN := false;
17015     do_remove       BOOLEAN := false;
17016 BEGIN
17017     add_query := $$
17018             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17019                 SELECT  cp.id, cp.circ_lib, cn.record
17020                   FROM  asset.copy cp
17021                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17022                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17023                         JOIN asset.copy_location cl ON (cp.location = cl.id)
17024                         JOIN config.copy_status cs ON (cp.status = cs.id)
17025                         JOIN biblio.record_entry b ON (cn.record = b.id)
17026                   WHERE NOT cp.deleted
17027                         AND NOT cn.deleted
17028                         AND NOT b.deleted
17029                         AND cs.opac_visible
17030                         AND cl.opac_visible
17031                         AND cp.opac_visible
17032                         AND a.opac_visible
17033     $$;
17034  
17035     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
17036
17037     IF TG_OP = 'INSERT' THEN
17038
17039         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17040             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17041             EXECUTE add_query;
17042         END IF;
17043
17044         RETURN NEW;
17045
17046     END IF;
17047
17048     -- handle items first, since with circulation activity
17049     -- their statuses change frequently
17050     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17051
17052         IF OLD.location    <> NEW.location OR
17053            OLD.call_number <> NEW.call_number OR
17054            OLD.status      <> NEW.status OR
17055            OLD.circ_lib    <> NEW.circ_lib THEN
17056             -- any of these could change visibility, but
17057             -- we'll save some queries and not try to calculate
17058             -- the change directly
17059             do_remove := true;
17060             do_add := true;
17061         ELSE
17062
17063             IF OLD.deleted <> NEW.deleted THEN
17064                 IF NEW.deleted THEN
17065                     do_remove := true;
17066                 ELSE
17067                     do_add := true;
17068                 END IF;
17069             END IF;
17070
17071             IF OLD.opac_visible <> NEW.opac_visible THEN
17072                 IF OLD.opac_visible THEN
17073                     do_remove := true;
17074                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
17075                                         -- is also marked opac_visible
17076                     do_add := true;
17077                 END IF;
17078             END IF;
17079
17080         END IF;
17081
17082         IF do_remove THEN
17083             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
17084         END IF;
17085         IF do_add THEN
17086             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17087             EXECUTE add_query;
17088         END IF;
17089
17090         RETURN NEW;
17091
17092     END IF;
17093
17094     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
17095  
17096         IF OLD.deleted AND NEW.deleted THEN -- do nothing
17097
17098             RETURN NEW;
17099  
17100         ELSIF NEW.deleted THEN -- remove rows
17101  
17102             IF TG_TABLE_NAME = 'call_number' THEN
17103                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
17104             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17105                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
17106             END IF;
17107  
17108             RETURN NEW;
17109  
17110         ELSIF OLD.deleted THEN -- add rows
17111  
17112             IF TG_TABLE_NAME IN ('copy','unit') THEN
17113                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17114             ELSIF TG_TABLE_NAME = 'call_number' THEN
17115                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17116             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17117                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
17118             END IF;
17119  
17120             EXECUTE add_query;
17121             RETURN NEW;
17122  
17123         END IF;
17124  
17125     END IF;
17126
17127     IF TG_TABLE_NAME = 'call_number' THEN
17128
17129         IF OLD.record <> NEW.record THEN
17130             -- call number is linked to different bib
17131             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
17132             EXECUTE remove_query;
17133             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17134             EXECUTE add_query;
17135         END IF;
17136
17137         RETURN NEW;
17138
17139     END IF;
17140
17141     IF TG_TABLE_NAME IN ('record_entry') THEN
17142         RETURN NEW; -- don't have 'opac_visible'
17143     END IF;
17144
17145     -- actor.org_unit, asset.copy_location, asset.copy_status
17146     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
17147
17148         RETURN NEW;
17149
17150     ELSIF NEW.opac_visible THEN -- add rows
17151
17152         IF TG_TABLE_NAME = 'org_unit' THEN
17153             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
17154         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17155             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
17156         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17157             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
17158         END IF;
17159  
17160         EXECUTE add_query;
17161  
17162     ELSE -- delete rows
17163
17164         IF TG_TABLE_NAME = 'org_unit' THEN
17165             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17166         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17167             remove_query := remove_query || 'location = ' || NEW.id || ');';
17168         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17169             remove_query := remove_query || 'status = ' || NEW.id || ');';
17170         END IF;
17171  
17172         EXECUTE remove_query;
17173  
17174     END IF;
17175  
17176     RETURN NEW;
17177 END;
17178 $func$ LANGUAGE PLPGSQL;
17179 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17180 Trigger function to update the copy OPAC visiblity cache.
17181 $$;
17182 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();
17183 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17184 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();
17185 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();
17186 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17187 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();
17188 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();
17189
17190 -- must create this rule explicitly; it is not inherited from asset.copy
17191 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;
17192
17193 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);
17194
17195 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17196 DECLARE
17197     moved_objects INT := 0;
17198     bib_id        INT := 0;
17199     bib_rec       biblio.record_entry%ROWTYPE;
17200     auth_link     authority.bib_linking%ROWTYPE;
17201 BEGIN
17202
17203     -- 1. Make source_record MARC a copy of the target_record to get auto-sync in linked bib records
17204     UPDATE authority.record_entry
17205       SET marc = (
17206         SELECT marc
17207           FROM authority.record_entry
17208           WHERE id = target_record
17209       )
17210       WHERE id = source_record;
17211
17212     -- 2. Update all bib records with the ID from target_record in their $0
17213     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre 
17214       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17215       WHERE abl.authority = target_record LOOP
17216
17217         UPDATE biblio.record_entry
17218           SET marc = REGEXP_REPLACE(marc, 
17219             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17220             E'\\1' || target_record || '<', 'g')
17221           WHERE id = bib_rec.id;
17222
17223           moved_objects := moved_objects + 1;
17224     END LOOP;
17225
17226     -- 3. "Delete" source_record
17227     DELETE FROM authority.record_entry
17228       WHERE id = source_record;
17229
17230     RETURN moved_objects;
17231 END;
17232 $func$ LANGUAGE plpgsql;
17233
17234 -- serial.record_entry already had an owner column spelled "owning_lib"
17235 -- Adjust the table and affected functions accordingly
17236
17237 ALTER TABLE serial.record_entry DROP COLUMN owner;
17238
17239 CREATE TABLE actor.usr_saved_search (
17240     id              SERIAL          PRIMARY KEY,
17241         owner           INT             NOT NULL REFERENCES actor.usr (id)
17242                                         ON DELETE CASCADE
17243                                         DEFERRABLE INITIALLY DEFERRED,
17244         name            TEXT            NOT NULL,
17245         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17246         query_text      TEXT            NOT NULL,
17247         query_type      TEXT            NOT NULL
17248                                         CONSTRAINT valid_query_text CHECK (
17249                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17250                                         -- we may add other types someday
17251         target          TEXT            NOT NULL
17252                                         CONSTRAINT valid_target CHECK (
17253                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17254         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17255 );
17256
17257 -- Apply Dan Wells' changes to the serial schema, from the
17258 -- seials-integration branch
17259
17260 CREATE TABLE serial.subscription_note (
17261         id           SERIAL PRIMARY KEY,
17262         subscription INT    NOT NULL
17263                             REFERENCES serial.subscription (id)
17264                             ON DELETE CASCADE
17265                             DEFERRABLE INITIALLY DEFERRED,
17266         creator      INT    NOT NULL
17267                             REFERENCES actor.usr (id)
17268                             DEFERRABLE INITIALLY DEFERRED,
17269         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17270         pub          BOOL   NOT NULL DEFAULT FALSE,
17271         title        TEXT   NOT NULL,
17272         value        TEXT   NOT NULL
17273 );
17274 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17275
17276 CREATE TABLE serial.distribution_note (
17277         id           SERIAL PRIMARY KEY,
17278         distribution INT    NOT NULL
17279                             REFERENCES serial.distribution (id)
17280                             ON DELETE CASCADE
17281                             DEFERRABLE INITIALLY DEFERRED,
17282         creator      INT    NOT NULL
17283                             REFERENCES actor.usr (id)
17284                             DEFERRABLE INITIALLY DEFERRED,
17285         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17286         pub          BOOL   NOT NULL DEFAULT FALSE,
17287         title        TEXT   NOT NULL,
17288         value        TEXT   NOT NULL
17289 );
17290 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17291
17292 ------- Begin surgery on serial.unit
17293
17294 ALTER TABLE serial.unit
17295         DROP COLUMN label;
17296
17297 ALTER TABLE serial.unit
17298         RENAME COLUMN label_sort_key TO sort_key;
17299
17300 ALTER TABLE serial.unit
17301         RENAME COLUMN contents TO detailed_contents;
17302
17303 ALTER TABLE serial.unit
17304         ADD COLUMN summary_contents TEXT;
17305
17306 UPDATE serial.unit
17307 SET summary_contents = detailed_contents;
17308
17309 ALTER TABLE serial.unit
17310         ALTER column summary_contents SET NOT NULL;
17311
17312 ------- End surgery on serial.unit
17313
17314 -- 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' );
17315
17316 -- Now rebuild the constraints dropped via cascade.
17317 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17318 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17319 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17320
17321 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17322
17323 DELETE FROM config.metabib_field_index_norm_map
17324     WHERE norm IN (
17325         SELECT id 
17326             FROM config.index_normalizer
17327             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17328     )
17329     AND field = 18
17330 ;
17331
17332 -- We won't necessarily use all of these, but they are here for completeness.
17333 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17334 -- Values are the EDI code value + 1200
17335
17336 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17337 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17338 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17339 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17340 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17341 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17342 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17343 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17344 (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.'),
17345 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17346 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17347 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17348 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17349 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17350 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17351 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17352 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17353 (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.'),
17354 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17355 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17356 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17357 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17358 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17359 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17360 (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.'),
17361 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17362 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17363 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17364 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17365 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17366 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17367 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17368 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17369 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17370 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17371 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17372 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17373 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17374 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17375 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17376 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17377 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17378 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17379 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17380 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17381 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17382 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17383 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17384 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17385 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17386 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17387 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17388 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17389 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17390 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17391 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17392 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17393 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17394 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17395 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17396 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17397 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17398 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17399 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17400 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17401 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17402 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17403 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17404 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17405 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17406 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17407 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17408 (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.'),
17409 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17410 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17411 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17412 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17413 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17414 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17415 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17416 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17417 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17418 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17419 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17420 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17421 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17422 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17423 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17424 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17425 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17426 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17427 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17428 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17429 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17430 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17431 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17432 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17433 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17434 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17435 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17436 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17437 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17438 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17439 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17440 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17441 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17442 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17443 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17444 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17445 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17446 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17447 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17448 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17449 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17450 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17451 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17452 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17453 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17454 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17455 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17456 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17457 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17458 (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.'),
17459 (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.'),
17460 (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.'),
17461 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17462 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17463 (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.'),
17464 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17465 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17466 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17467 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17468 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17469 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17470 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17471 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17472 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17473 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17474 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17475 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17476 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17477 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17478 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17479 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17480 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17481 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17482 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17483 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17484 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17485 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17486 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17487 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17488 (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.'),
17489 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17490 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17491 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17492 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17493 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17494 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17495 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17496 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17497 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17498 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17499 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17500 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17501 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17502 (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.'),
17503 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17504 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17505 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17506 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17507 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17508 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17509 (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.'),
17510 (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.'),
17511 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17512 (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.'),
17513 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17514 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17515 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17516 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17517 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17518 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17519 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17520 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17521 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17522 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17523 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17524 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17525 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17526 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17527 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17528 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17529 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17530 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17531 (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.'),
17532 (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.'),
17533 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17534 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17535 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17536 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17537 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17538 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17539 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17540 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17541 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17542 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17543 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17544 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17545 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17546 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17547 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17548 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17549 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17550 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17551 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17552 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17553 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17554 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17555 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17556 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17557 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17558 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17559 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17560 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17561 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17562 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17563 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17564 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17565 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17566 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17567 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17568 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17569 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17570 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17571 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17572 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17573 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17574 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17575 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17576 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17577 (1, 't', 1442, 'Number of months', 'The number of months.'),
17578 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17579 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17580 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17581 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17582 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17583 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17584 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17585 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17586 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17587 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17588 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17589 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17590 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17591 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17592 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17593 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17594 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17595 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17596 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17597 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17598 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17599 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17600 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17601 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17602 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17603 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17604 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17605 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17606 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17607 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17608 (1, 't', 1473, 'Agents', 'The number of agents.'),
17609 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17610 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17611 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17612 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17613 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17614 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17615 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17616 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17617 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17618 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17619 (1, 't', 1484, 'Departments', 'The number of departments.'),
17620 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17621 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17622 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17623 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17624 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17625 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17626 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17627 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17628 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17629 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17630 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17631 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17632 (1, 't', 1497, 'Executives', 'The number of executives.'),
17633 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17634 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17635 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17636 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17637 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17638 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17639 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17640 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17641 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17642 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17643 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17644 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17645 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17646 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17647 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17648 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17649 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17650 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17651 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17652 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17653 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17654 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17655 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17656 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17657 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17658 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17659 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17660 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17661 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17662 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17663 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17664 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17665 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17666 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17667 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17668 (1, 't', 1533, 'Seats',        'The number of seats.'),
17669 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17670 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17671 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17672 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17673 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17674 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17675 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17676 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17677 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17678 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17679 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17680 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17681 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17682 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17683 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17684 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17685 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17686 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17687 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17688 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17689 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17690 (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.'),
17691 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17692 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17693 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17694 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17695 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17696 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17697 (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.'),
17698 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17699 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17700 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17701 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17702 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17703 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17704 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17705 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17706 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17707 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17708 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17709 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17710 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17711 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17712 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17713 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17714 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17715 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17716 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17717 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17718 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17719 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17720 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17721 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17722 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17723 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17724 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17725 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17726 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17727 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17728 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17729 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17730 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17731 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17732 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17733 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17734 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17735 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17736 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17737 (1, 't', 1602, 'Patients',         'Number of patients.'),
17738 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17739 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17740 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17741 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17742 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17743 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17744 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17745 (1, 't', 1610, 'Operators',        'Number of operators.'),
17746 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17747 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17748 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17749 (1, 't', 1614, 'Machines',         'Number of machines.'),
17750 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17751 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17752 (1, 't', 1617, 'Directors',        'Number of directors.'),
17753 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17754 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17755 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17756 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17757 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17758 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17759 (1, 't', 1624, 'Beds', 'Number of beds.'),
17760 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17761 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17762 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17763 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
17764 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
17765 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
17766 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
17767 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
17768 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
17769 (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.'),
17770 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
17771 (1, 't', 1636, 'Professor', 'The number of professors.'),
17772 (1, 't', 1637, 'Seller',    'The number of sellers.'),
17773 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
17774 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
17775 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
17776 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
17777 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
17778 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
17779 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
17780 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
17781 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
17782 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
17783 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
17784 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
17785 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
17786 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
17787 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
17788 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
17789 (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.'),
17790 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
17791 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
17792 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
17793 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
17794 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
17795 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
17796 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
17797 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
17798 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
17799 (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.'),
17800 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
17801 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
17802 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
17803 (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.'),
17804 (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.'),
17805 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
17806 (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.'),
17807 (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.'),
17808 (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.'),
17809 (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.'),
17810 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
17811 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
17812 (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.'),
17813 (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.'),
17814 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
17815 (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.'),
17816 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
17817 (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.'),
17818 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
17819 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
17820 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
17821 (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).'),
17822 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
17823 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
17824 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
17825 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
17826 ;
17827 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
17828
17829 CREATE TABLE acq.serial_claim (
17830     id     SERIAL           PRIMARY KEY,
17831     type   INT              NOT NULL REFERENCES acq.claim_type
17832                                      DEFERRABLE INITIALLY DEFERRED,
17833     item    BIGINT          NOT NULL REFERENCES serial.item
17834                                      DEFERRABLE INITIALLY DEFERRED
17835 );
17836
17837 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
17838
17839 CREATE TABLE acq.serial_claim_event (
17840     id             BIGSERIAL        PRIMARY KEY,
17841     type           INT              NOT NULL REFERENCES acq.claim_event_type
17842                                              DEFERRABLE INITIALLY DEFERRED,
17843     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
17844                                              DEFERRABLE INITIALLY DEFERRED,
17845     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
17846     creator        INT              NOT NULL REFERENCES actor.usr
17847                                              DEFERRABLE INITIALLY DEFERRED,
17848     note           TEXT
17849 );
17850
17851 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
17852
17853 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
17854
17855 -- now what about the auditor.*_lifecycle views??
17856
17857 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17858     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
17859 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17860     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
17861 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
17862 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
17863
17864 CREATE TABLE asset.call_number_class (
17865     id             bigserial     PRIMARY KEY,
17866     name           TEXT          NOT NULL,
17867     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
17868     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17869 );
17870
17871 COMMENT ON TABLE asset.call_number_class IS $$
17872 Defines the call number normalization database functions in the "normalizer"
17873 column and the tag/subfield combinations to use to lookup the call number in
17874 the "field" column for a given classification scheme. Tag/subfield combinations
17875 are delimited by commas.
17876 $$;
17877
17878 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
17879     ('Generic', 'asset.label_normalizer_generic'),
17880     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
17881     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
17882 ;
17883
17884 -- Generic fields
17885 UPDATE asset.call_number_class
17886     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17887     WHERE id = 1
17888 ;
17889
17890 -- Dewey fields
17891 UPDATE asset.call_number_class
17892     SET field = '080ab,082ab'
17893     WHERE id = 2
17894 ;
17895
17896 -- LC fields
17897 UPDATE asset.call_number_class
17898     SET field = '050ab,055ab'
17899     WHERE id = 3
17900 ;
17901  
17902 ALTER TABLE asset.call_number
17903         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
17904                 REFERENCES asset.call_number_class(id)
17905                 DEFERRABLE INITIALLY DEFERRED;
17906
17907 ALTER TABLE asset.call_number
17908         ADD COLUMN label_sortkey TEXT;
17909
17910 CREATE INDEX asset_call_number_label_sortkey
17911         ON asset.call_number(oils_text_as_bytea(label_sortkey));
17912
17913 ALTER TABLE auditor.asset_call_number_history
17914         ADD COLUMN label_class BIGINT;
17915
17916 ALTER TABLE auditor.asset_call_number_history
17917         ADD COLUMN label_sortkey TEXT;
17918
17919 -- Pick up the new columns in dependent views
17920
17921 DROP VIEW auditor.asset_call_number_lifecycle;
17922
17923 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17924
17925 DROP VIEW auditor.asset_call_number_lifecycle;
17926
17927 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17928
17929 DROP VIEW IF EXISTS stats.fleshed_call_number;
17930
17931 CREATE VIEW stats.fleshed_call_number AS
17932         SELECT  cn.*,
17933             CAST(cn.create_date AS DATE) AS create_date_day,
17934         CAST(cn.edit_date AS DATE) AS edit_date_day,
17935         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
17936         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
17937             rd.item_lang,
17938                 rd.item_type,
17939                 rd.item_form
17940         FROM    asset.call_number cn
17941                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
17942
17943 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
17944 DECLARE
17945     sortkey        TEXT := '';
17946 BEGIN
17947     sortkey := NEW.label_sortkey;
17948
17949     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
17950        quote_literal( NEW.label ) || ')'
17951        FROM asset.call_number_class acnc
17952        WHERE acnc.id = NEW.label_class
17953        INTO sortkey;
17954
17955     NEW.label_sortkey = sortkey;
17956
17957     RETURN NEW;
17958 END;
17959 $func$ LANGUAGE PLPGSQL;
17960
17961 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
17962     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
17963     # thus could probably be considered a derived work, although nothing was
17964     # directly copied - but to err on the safe side of providing attribution:
17965     # Copyright (C) 2007 LibLime
17966     # Licensed under the GPL v2 or later
17967
17968     use strict;
17969     use warnings;
17970
17971     # Converts the callnumber to uppercase
17972     # Strips spaces from start and end of the call number
17973     # Converts anything other than letters, digits, and periods into underscores
17974     # Collapses multiple underscores into a single underscore
17975     my $callnum = uc(shift);
17976     $callnum =~ s/^\s//g;
17977     $callnum =~ s/\s$//g;
17978     $callnum =~ s/[^A-Z0-9_.]/_/g;
17979     $callnum =~ s/_{2,}/_/g;
17980
17981     return $callnum;
17982 $func$ LANGUAGE PLPERLU;
17983
17984 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
17985     # Derived from the Koha C4::ClassSortRoutine::Dewey module
17986     # Copyright (C) 2007 LibLime
17987     # Licensed under the GPL v2 or later
17988
17989     use strict;
17990     use warnings;
17991
17992     my $init = uc(shift);
17993     $init =~ s/^\s+//;
17994     $init =~ s/\s+$//;
17995     $init =~ s!/!!g;
17996     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
17997     my @tokens = split /\.|\s+/, $init;
17998     my $digit_group_count = 0;
17999     for (my $i = 0; $i <= $#tokens; $i++) {
18000         if ($tokens[$i] =~ /^\d+$/) {
18001             $digit_group_count++;
18002             if (2 == $digit_group_count) {
18003                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
18004                 $tokens[$i] =~ tr/ /0/;
18005             }
18006         }
18007     }
18008     my $key = join("_", @tokens);
18009     $key =~ s/[^\p{IsAlnum}_]//g;
18010
18011     return $key;
18012
18013 $func$ LANGUAGE PLPERLU;
18014
18015 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
18016     use strict;
18017     use warnings;
18018
18019     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
18020     # The author hopes to upload it to CPAN some day, which would make our lives easier
18021     use Library::CallNumber::LC;
18022
18023     my $callnum = Library::CallNumber::LC->new(shift);
18024     return $callnum->normalize();
18025
18026 $func$ LANGUAGE PLPERLU;
18027
18028 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$
18029 DECLARE
18030     ans RECORD;
18031     trans INT;
18032 BEGIN
18033     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;
18034
18035     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
18036         RETURN QUERY
18037         SELECT  ans.depth,
18038                 ans.id,
18039                 COUNT( av.id ),
18040                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18041                 COUNT( av.id ),
18042                 trans
18043           FROM
18044                 actor.org_unit_descendants(ans.id) d
18045                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18046                 JOIN asset.copy cp ON (cp.id = av.id)
18047           GROUP BY 1,2,6;
18048
18049         IF NOT FOUND THEN
18050             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18051         END IF;
18052
18053     END LOOP;
18054
18055     RETURN;
18056 END;
18057 $f$ LANGUAGE PLPGSQL;
18058
18059 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$
18060 DECLARE
18061     ans RECORD;
18062     trans INT;
18063 BEGIN
18064     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;
18065
18066     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18067         RETURN QUERY
18068         SELECT  -1,
18069                 ans.id,
18070                 COUNT( av.id ),
18071                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18072                 COUNT( av.id ),
18073                 trans
18074           FROM
18075                 actor.org_unit_descendants(ans.id) d
18076                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18077                 JOIN asset.copy cp ON (cp.id = av.id)
18078           GROUP BY 1,2,6;
18079
18080         IF NOT FOUND THEN
18081             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18082         END IF;
18083
18084     END LOOP;
18085
18086     RETURN;
18087 END;
18088 $f$ LANGUAGE PLPGSQL;
18089
18090 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$
18091 DECLARE
18092     ans RECORD;
18093     trans INT;
18094 BEGIN
18095     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;
18096
18097     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
18098         RETURN QUERY
18099         SELECT  ans.depth,
18100                 ans.id,
18101                 COUNT( cp.id ),
18102                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18103                 COUNT( cp.id ),
18104                 trans
18105           FROM
18106                 actor.org_unit_descendants(ans.id) d
18107                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18108                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18109           GROUP BY 1,2,6;
18110
18111         IF NOT FOUND THEN
18112             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18113         END IF;
18114
18115     END LOOP;
18116
18117     RETURN;
18118 END;
18119 $f$ LANGUAGE PLPGSQL;
18120
18121 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$
18122 DECLARE
18123     ans RECORD;
18124     trans INT;
18125 BEGIN
18126     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;
18127
18128     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18129         RETURN QUERY
18130         SELECT  -1,
18131                 ans.id,
18132                 COUNT( cp.id ),
18133                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18134                 COUNT( cp.id ),
18135                 trans
18136           FROM
18137                 actor.org_unit_descendants(ans.id) d
18138                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18139                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18140           GROUP BY 1,2,6;
18141
18142         IF NOT FOUND THEN
18143             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18144         END IF;
18145
18146     END LOOP;
18147
18148     RETURN;
18149 END;
18150 $f$ LANGUAGE PLPGSQL;
18151
18152 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$
18153 BEGIN
18154     IF staff IS TRUE THEN
18155         IF place > 0 THEN
18156             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
18157         ELSE
18158             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
18159         END IF;
18160     ELSE
18161         IF place > 0 THEN
18162             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18163         ELSE
18164             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18165         END IF;
18166     END IF;
18167
18168     RETURN;
18169 END;
18170 $f$ LANGUAGE PLPGSQL;
18171
18172 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$
18173 DECLARE
18174     ans RECORD;
18175     trans INT;
18176 BEGIN
18177     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;
18178
18179     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
18180         RETURN QUERY
18181         SELECT  ans.depth,
18182                 ans.id,
18183                 COUNT( av.id ),
18184                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18185                 COUNT( av.id ),
18186                 trans
18187           FROM
18188                 actor.org_unit_descendants(ans.id) d
18189                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18190                 JOIN asset.copy cp ON (cp.id = av.id)
18191                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18192           GROUP BY 1,2,6;
18193
18194         IF NOT FOUND THEN
18195             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18196         END IF;
18197
18198     END LOOP;
18199
18200     RETURN;
18201 END;
18202 $f$ LANGUAGE PLPGSQL;
18203
18204 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$
18205 DECLARE
18206     ans RECORD;
18207     trans INT;
18208 BEGIN
18209     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;
18210
18211     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18212         RETURN QUERY
18213         SELECT  -1,
18214                 ans.id,
18215                 COUNT( av.id ),
18216                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18217                 COUNT( av.id ),
18218                 trans
18219           FROM
18220                 actor.org_unit_descendants(ans.id) d
18221                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18222                 JOIN asset.copy cp ON (cp.id = av.id)
18223                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18224           GROUP BY 1,2,6;
18225
18226         IF NOT FOUND THEN
18227             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18228         END IF;
18229
18230     END LOOP;
18231
18232     RETURN;
18233 END;
18234 $f$ LANGUAGE PLPGSQL;
18235
18236 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$
18237 DECLARE
18238     ans RECORD;
18239     trans INT;
18240 BEGIN
18241     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;
18242
18243     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
18244         RETURN QUERY
18245         SELECT  ans.depth,
18246                 ans.id,
18247                 COUNT( cp.id ),
18248                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18249                 COUNT( cp.id ),
18250                 trans
18251           FROM
18252                 actor.org_unit_descendants(ans.id) d
18253                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18254                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18255                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18256           GROUP BY 1,2,6;
18257
18258         IF NOT FOUND THEN
18259             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18260         END IF;
18261
18262     END LOOP;
18263
18264     RETURN;
18265 END;
18266 $f$ LANGUAGE PLPGSQL;
18267
18268 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$
18269 DECLARE
18270     ans RECORD;
18271     trans INT;
18272 BEGIN
18273     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;
18274
18275     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18276         RETURN QUERY
18277         SELECT  -1,
18278                 ans.id,
18279                 COUNT( cp.id ),
18280                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18281                 COUNT( cp.id ),
18282                 trans
18283           FROM
18284                 actor.org_unit_descendants(ans.id) d
18285                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18286                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18287                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18288           GROUP BY 1,2,6;
18289
18290         IF NOT FOUND THEN
18291             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18292         END IF;
18293
18294     END LOOP;
18295
18296     RETURN;
18297 END;
18298 $f$ LANGUAGE PLPGSQL;
18299
18300 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$
18301 BEGIN
18302     IF staff IS TRUE THEN
18303         IF place > 0 THEN
18304             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18305         ELSE
18306             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18307         END IF;
18308     ELSE
18309         IF place > 0 THEN
18310             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18311         ELSE
18312             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18313         END IF;
18314     END IF;
18315
18316     RETURN;
18317 END;
18318 $f$ LANGUAGE PLPGSQL;
18319
18320 -- No transaction is required
18321
18322 -- Triggers on the vandelay.queued_*_record tables delete entries from
18323 -- the associated vandelay.queued_*_record_attr tables based on the record's
18324 -- ID; create an index on that column to avoid sequential scans for each
18325 -- queued record that is deleted
18326 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18327 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18328
18329 -- Avoid sequential scans for queue retrieval operations by providing an
18330 -- index on the queue column
18331 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18332 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18333
18334 -- Start picking up call number label prefixes and suffixes
18335 -- from asset.copy_location
18336 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18337 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18338
18339 DROP VIEW auditor.asset_copy_lifecycle;
18340
18341 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18342
18343 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18344
18345 -- Let's not break existing reports
18346 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18347 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18348
18349 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18350 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18351 SELECT  r.id,
18352     r.fingerprint,
18353     r.quality,
18354     r.tcn_source,
18355     r.tcn_value,
18356     FIRST(title.value) AS title,
18357     FIRST(author.value) AS author,
18358     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18359     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18360     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18361     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18362   FROM  biblio.record_entry r
18363     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18364     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18365     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18366     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18367     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18368     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18369   GROUP BY 1,2,3,4,5;
18370
18371 -- Correct the ISSN array definition for reporter.simple_record
18372
18373 CREATE OR REPLACE VIEW reporter.simple_record AS
18374 SELECT  r.id,
18375         s.metarecord,
18376         r.fingerprint,
18377         r.quality,
18378         r.tcn_source,
18379         r.tcn_value,
18380         title.value AS title,
18381         uniform_title.value AS uniform_title,
18382         author.value AS author,
18383         publisher.value AS publisher,
18384         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18385         series_title.value AS series_title,
18386         series_statement.value AS series_statement,
18387         summary.value AS summary,
18388         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18389         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18390         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18391         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18392         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18393         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18394         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18395         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
18396   FROM  biblio.record_entry r
18397         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18398         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18399         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18400         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18401         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18402         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18403         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18404         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18405         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')
18406         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18407         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18408   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18409
18410 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18411     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
18412 $$ LANGUAGE SQL;
18413
18414 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18415 BEGIN
18416     IF TG_OP = 'DELETE' THEN
18417         PERFORM reporter.simple_rec_delete(NEW.id);
18418     ELSE
18419         PERFORM reporter.simple_rec_update(NEW.id);
18420     END IF;
18421
18422     RETURN NEW;
18423 END;
18424 $func$ LANGUAGE PLPGSQL;
18425
18426 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
18427
18428 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18429
18430 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18431
18432 UPDATE config.org_unit_setting_type
18433     SET view_perm = (SELECT id FROM permission.perm_list
18434         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18435     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18436
18437 UPDATE config.org_unit_setting_type
18438     SET update_perm = (SELECT id FROM permission.perm_list
18439         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18440     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18441
18442 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18443     VALUES (
18444         'opac.fully_compressed_serial_holdings',
18445         'OPAC: Use fully compressed serial holdings',
18446         'Show fully compressed serial holdings for all libraries at and below
18447         the current context unit',
18448         'bool'
18449     );
18450
18451 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18452     use strict;
18453     use warnings;
18454
18455     use utf8;
18456     use MARC::Record;
18457     use MARC::File::XML (BinaryEncoding => 'UTF8');
18458     use UUID::Tiny ':std';
18459
18460     my $xml = shift() or return undef;
18461
18462     my $r;
18463
18464     # Prevent errors in XML parsing from blowing out ungracefully
18465     eval {
18466         $r = MARC::Record->new_from_xml( $xml );
18467         1;
18468     } or do {
18469        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18470     };
18471
18472     if (!$r) {
18473        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18474     }
18475
18476     # From http://www.loc.gov/standards/sourcelist/subject.html
18477     my $thes_code_map = {
18478         a => 'lcsh',
18479         b => 'lcshac',
18480         c => 'mesh',
18481         d => 'nal',
18482         k => 'cash',
18483         n => 'notapplicable',
18484         r => 'aat',
18485         s => 'sears',
18486         v => 'rvm',
18487     };
18488
18489     # Default to "No attempt to code" if the leader is horribly broken
18490     my $fixed_field = $r->field('008');
18491     my $thes_char = '|';
18492     if ($fixed_field) {
18493         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18494     }
18495
18496     my $thes_code = 'UNDEFINED';
18497
18498     if ($thes_char eq 'z') {
18499         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18500         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18501     } elsif ($thes_code_map->{$thes_char}) {
18502         $thes_code = $thes_code_map->{$thes_char};
18503     }
18504
18505     my $auth_txt = '';
18506     my $head = $r->field('1..');
18507     if ($head) {
18508         # Concatenate all of these subfields together, prefixed by their code
18509         # to prevent collisions along the lines of "Fiction, North Carolina"
18510         foreach my $sf ($head->subfields()) {
18511             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18512         }
18513     }
18514
18515     # Perhaps better to parameterize the spi and pass as a parameter
18516     $auth_txt =~ s/'//go;
18517
18518     if ($auth_txt) {
18519         my $result = spi_exec_query("SELECT public.naco_normalize('$auth_txt') AS norm_text");
18520         my $norm_txt = $result->{rows}[0]->{norm_text};
18521         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18522     }
18523
18524     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18525 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18526
18527 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18528 /**
18529 * Extract the authority heading, thesaurus, and NACO-normalized values
18530 * from an authority record. The primary purpose is to build a unique
18531 * index to defend against duplicated authority records from the same
18532 * thesaurus.
18533 */
18534 $$;
18535
18536 DROP INDEX authority.authority_record_unique_tcn;
18537 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18538 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18539
18540 ALTER TABLE acq.provider_contact
18541         ALTER COLUMN name SET NOT NULL;
18542
18543 ALTER TABLE actor.stat_cat
18544         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18545
18546 -- Recreate some foreign keys that were somehow dropped, probably
18547 -- by some kind of cascade from an inherited table:
18548
18549 ALTER TABLE action.reservation_transit_copy
18550         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
18551                 REFERENCES booking.resource(id)
18552                 ON DELETE CASCADE
18553                 DEFERRABLE INITIALLY DEFERRED,
18554         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
18555                 REFERENCES booking.reservation(id)
18556                 ON DELETE SET NULL
18557                 DEFERRABLE INITIALLY DEFERRED;
18558
18559 CREATE INDEX user_bucket_item_target_user_idx
18560         ON container.user_bucket_item ( target_user );
18561
18562 CREATE INDEX m_c_t_collector_idx
18563         ON money.collections_tracker ( collector );
18564
18565 CREATE INDEX aud_actor_usr_address_hist_id_idx
18566         ON auditor.actor_usr_address_history ( id );
18567
18568 CREATE INDEX aud_actor_usr_hist_id_idx
18569         ON auditor.actor_usr_history ( id );
18570
18571 CREATE INDEX aud_asset_cn_hist_creator_idx
18572         ON auditor.asset_call_number_history ( creator );
18573
18574 CREATE INDEX aud_asset_cn_hist_editor_idx
18575         ON auditor.asset_call_number_history ( editor );
18576
18577 CREATE INDEX aud_asset_cp_hist_creator_idx
18578         ON auditor.asset_copy_history ( creator );
18579
18580 CREATE INDEX aud_asset_cp_hist_editor_idx
18581         ON auditor.asset_copy_history ( editor );
18582
18583 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18584         ON auditor.biblio_record_entry_history ( creator );
18585
18586 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18587         ON auditor.biblio_record_entry_history ( editor );
18588
18589 CREATE TABLE action.hold_request_note (
18590
18591     id     BIGSERIAL PRIMARY KEY,
18592     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18593                               ON DELETE CASCADE
18594                               DEFERRABLE INITIALLY DEFERRED,
18595     title  TEXT      NOT NULL,
18596     body   TEXT      NOT NULL,
18597     slip   BOOL      NOT NULL DEFAULT FALSE,
18598     pub    BOOL      NOT NULL DEFAULT FALSE,
18599     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18600
18601 );
18602 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18603
18604 -- Tweak a constraint to add a CASCADE
18605
18606 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18607
18608 ALTER TABLE action.hold_notification
18609         ADD CONSTRAINT hold_notification_hold_fkey
18610                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18611                 ON DELETE CASCADE
18612                 DEFERRABLE INITIALLY DEFERRED;
18613
18614 CREATE TRIGGER asset_label_sortkey_trigger
18615     BEFORE UPDATE OR INSERT ON asset.call_number
18616     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18617
18618 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18619 RETURNS VOID AS $$
18620 --
18621 -- Delete expired circulation bucket items for all users that have
18622 -- a setting for patron.max_reading_list_interval.
18623 --
18624 DECLARE
18625     today        TIMESTAMP WITH TIME ZONE;
18626     threshold    TIMESTAMP WITH TIME ZONE;
18627         usr_setting  RECORD;
18628 BEGIN
18629         SELECT date_trunc( 'day', now() ) INTO today;
18630         --
18631         FOR usr_setting in
18632                 SELECT
18633                         usr,
18634                         value
18635                 FROM
18636                         actor.usr_setting
18637                 WHERE
18638                         name = 'patron.max_reading_list_interval'
18639         LOOP
18640                 --
18641                 -- Make sure the setting is a valid interval
18642                 --
18643                 BEGIN
18644                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18645                 EXCEPTION
18646                         WHEN OTHERS THEN
18647                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18648                                         usr_setting.usr, usr_setting.value;
18649                                 CONTINUE;
18650                 END;
18651                 --
18652                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18653                 --
18654         DELETE FROM container.copy_bucket_item
18655         WHERE
18656                 bucket IN
18657                 (
18658                     SELECT
18659                         id
18660                     FROM
18661                         container.copy_bucket
18662                     WHERE
18663                         owner = usr_setting.usr
18664                         AND btype = 'circ_history'
18665                 )
18666                 AND create_time < threshold;
18667         END LOOP;
18668         --
18669 END;
18670 $$ LANGUAGE plpgsql;
18671
18672 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18673 /*
18674  * Delete expired circulation bucket items for all users that have
18675  * a setting for patron.max_reading_list_interval.
18676 */
18677 $$;
18678
18679 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18680          ac_usr IN INTEGER
18681 ) RETURNS VOID AS $$
18682 --
18683 -- Delete old circulation bucket items for a specified user.
18684 -- "Old" means older than the interval specified by a
18685 -- user-level setting, if it is so specified.
18686 --
18687 DECLARE
18688     threshold TIMESTAMP WITH TIME ZONE;
18689 BEGIN
18690         -- Sanity check
18691         IF ac_usr IS NULL THEN
18692                 RETURN;
18693         END IF;
18694         -- Determine the threshold date that defines "old".  Subtract the
18695         -- interval from the system date, then truncate to midnight.
18696         SELECT
18697                 date_trunc( 
18698                         'day',
18699                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18700                 )
18701         INTO
18702                 threshold
18703         FROM
18704                 actor.usr_setting
18705         WHERE
18706                 usr = ac_usr
18707                 AND name = 'patron.max_reading_list_interval';
18708         --
18709         IF threshold is null THEN
18710                 -- No interval defined; don't delete anything
18711                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18712                 return;
18713         END IF;
18714         --
18715         -- RAISE NOTICE 'Date threshold: %', threshold;
18716         --
18717         -- Threshold found; do the delete
18718         delete from container.copy_bucket_item
18719         where
18720                 bucket in
18721                 (
18722                         select
18723                                 id
18724                         from
18725                                 container.copy_bucket
18726                         where
18727                                 owner = ac_usr
18728                                 and btype = 'circ_history'
18729                 )
18730                 and create_time < threshold;
18731         --
18732         RETURN;
18733 END;
18734 $$ LANGUAGE plpgsql;
18735
18736 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18737 /*
18738  * Delete old circulation bucket items for a specified user.
18739  * "Old" means older than the interval specified by a
18740  * user-level setting, if it is so specified.
18741 */
18742 $$;
18743
18744 CREATE OR REPLACE VIEW reporter.hold_request_record AS
18745 SELECT  id,
18746     target,
18747     hold_type,
18748     CASE
18749         WHEN hold_type = 'T'
18750             THEN target
18751         WHEN hold_type = 'I'
18752             THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
18753         WHEN hold_type = 'V'
18754             THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
18755         WHEN hold_type IN ('C','R','F')
18756             THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
18757         WHEN hold_type = 'M'
18758             THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
18759     END AS bib_record
18760   FROM  action.hold_request ahr;
18761
18762 UPDATE  metabib.rec_descriptor
18763   SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
18764         date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
18765
18766 -- Change some ints to bigints:
18767
18768 ALTER TABLE container.biblio_record_entry_bucket_item
18769         ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
18770
18771 ALTER TABLE vandelay.queued_bib_record
18772         ALTER COLUMN imported_as SET DATA TYPE bigint;
18773
18774 ALTER TABLE action.hold_copy_map
18775         ALTER COLUMN id SET DATA TYPE bigint;
18776
18777 -- Make due times get pushed to 23:59:59 on insert OR update
18778 DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
18779 CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
18780
18781 COMMIT;
18782
18783 -- Some operations go outside of the transaction, because they may
18784 -- legitimately fail.
18785
18786 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
18787 \qecho doesn't exist; ignore those errors if they occur.
18788
18789 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
18790
18791 ALTER TABLE auditor.action_hold_request_history
18792 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
18793
18794 ALTER TABLE auditor.action_hold_request_history
18795 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
18796
18797 \qecho Outside of the transaction: adding indexes that may or may not exist.
18798 \qecho If any of these CREATE INDEX statements fails because the index already
18799 \qecho exists, ignore the failure.
18800
18801 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
18802 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
18803 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
18804 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
18805 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
18806 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
18807 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
18808 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
18809 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
18810 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
18811 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
18812 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
18813 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
18814 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
18815 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
18816 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
18817 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
18818 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
18819 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
18820 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
18821 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
18822 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
18823 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
18824 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
18825 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
18826 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
18827 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
18828 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
18829 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
18830 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
18831 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
18832 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
18833 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
18834
18835 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
18836
18837 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
18838
18839 \qecho If the following CREATE INDEX fails, It will be necessary to do some
18840 \qecho data cleanup as described in the comments.
18841
18842 CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
18843     ON authority.record_entry (authority.normalize_heading(marc))
18844         WHERE deleted IS FALSE or deleted = FALSE;
18845
18846 -- If the unique index fails, uncomment the following to create
18847 -- a regular index that will help find the duplicates in a hurry:
18848 --CREATE INDEX by_heading_and_thesaurus
18849 --    ON authority.record_entry (authority.normalize_heading(marc))
18850 --    WHERE deleted IS FALSE or deleted = FALSE
18851 --;
18852
18853 -- Then find the duplicates like so to get an idea of how much
18854 -- pain you're looking at to clean things up:
18855 --SELECT id, authority.normalize_heading(marc)
18856 --    FROM authority.record_entry
18857 --    WHERE authority.normalize_heading(marc) IN (
18858 --        SELECT authority.normalize_heading(marc)
18859 --        FROM authority.record_entry
18860 --        GROUP BY authority.normalize_heading(marc)
18861 --        HAVING COUNT(*) > 1
18862 --    )
18863 --;
18864
18865 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
18866 -- statement succeeds, drop the temporary index to avoid unnecessary
18867 -- duplication:
18868 -- DROP INDEX authority.by_heading_and_thesaurus;
18869
18870 \qecho Upgrade script completed.
18871