]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
Resolve differences between stored procedures in a freshly installed
[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 \qecho Before starting the transaction: drop some constraints.
5 \qecho If a DROP fails because the constraint doesn't exist, ignore the failure.
6
7 ALTER TABLE permission.grp_perm_map        DROP CONSTRAINT grp_perm_map_perm_fkey;
8 ALTER TABLE permission.usr_perm_map        DROP CONSTRAINT usr_perm_map_perm_fkey;
9 ALTER TABLE permission.usr_object_perm_map DROP CONSTRAINT usr_object_perm_map_perm_fkey;
10
11 \qecho Beginning the transaction now
12
13 BEGIN;
14
15 -- Highest-numbered individual upgrade script incorporated herein:
16
17 INSERT INTO config.upgrade_log (version) VALUES ('0418');
18
19 -- Begin by upgrading permission.perm_list.  This is fairly complicated.
20
21 -- Add ON UPDATE CASCADE to some foreign keys so that, when we renumber the
22 -- permissions, the dependents will follow and stay in sync:
23
24 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
25     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
26
27 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_perm_map_perm_fkey FOREIGN KEY (perm)
28     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
29
30 ALTER TABLE permission.usr_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
31     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
32
33 UPDATE permission.perm_list
34     SET code = 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow'
35     WHERE code = 'UPDATE_ORG_UNIT_SETTING.global.credit.allow';
36
37 -- The following UPDATES were originally in an individual upgrade script, but should
38 -- no longer be necessary now that the foreign key has an ON UPDATE CASCADE clause.
39 -- We retain the UPDATES here, commented out, as historical relics.
40
41 -- UPDATE permission.grp_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
42 -- UPDATE permission.usr_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
43
44 -- Spelling correction
45 UPDATE permission.perm_list SET code = 'ADMIN_RECURRING_FINE_RULE' WHERE code = 'ADMIN_RECURING_FINE_RULE';
46
47 -- Now we engage in a Great Renumbering of the permissions in permission.perm_list,
48 -- in order to clean up accumulated cruft.
49
50 -- The first step is to establish some triggers so that, when we change the id of a permission,
51 -- the associated translations are updated accordingly.
52
53 CREATE OR REPLACE FUNCTION oils_i18n_update_apply(old_ident TEXT, new_ident TEXT, hint TEXT) RETURNS VOID AS $_$
54 BEGIN
55
56     EXECUTE $$
57         UPDATE  config.i18n_core
58           SET   identity_value = $$ || quote_literal( new_ident ) || $$ 
59           WHERE fq_field LIKE '$$ || hint || $$.%' 
60                 AND identity_value = $$ || quote_literal( old_ident ) || $$;$$;
61
62     RETURN;
63
64 END;
65 $_$ LANGUAGE PLPGSQL;
66
67 CREATE OR REPLACE FUNCTION oils_i18n_id_tracking(/* hint */) RETURNS TRIGGER AS $_$
68 BEGIN
69     PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
70     RETURN NEW;
71 END;
72 $_$ LANGUAGE PLPGSQL;
73
74 CREATE OR REPLACE FUNCTION oils_i18n_code_tracking(/* hint */) RETURNS TRIGGER AS $_$
75 BEGIN
76     PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
77     RETURN NEW;
78 END;
79 $_$ LANGUAGE PLPGSQL;
80
81
82 CREATE TRIGGER maintain_perm_i18n_tgr
83     AFTER UPDATE ON permission.perm_list
84     FOR EACH ROW EXECUTE PROCEDURE oils_i18n_id_tracking('ppl');
85
86 -- Next, create a new table as a convenience for sloshing data back and forth,
87 -- and for recording which permission went where.  It looks just like
88 -- permission.perm_list, but with two extra columns: one for the old id, and one to
89 -- distinguish between predefined permissions and non-predefined permissions.
90
91 -- This table is, in effect, a temporary table, because we can drop it once the
92 -- upgrade is complete.  It is not technically temporary as far as PostgreSQL is
93 -- concerned, because we don't want it to disappear at the end of the session.
94 -- We keep it around so that we have a map showing the old id and the new id for
95 -- each permission.  However there is no IDL entry for it, nor is it defined
96 -- in the base sql files.
97
98 CREATE TABLE permission.temp_perm (
99         id          INT        PRIMARY KEY,
100         code        TEXT       UNIQUE,
101         description TEXT,
102         old_id      INT,
103         predefined  BOOL       NOT NULL DEFAULT TRUE
104 );
105
106 -- Populate the temp table with a definitive set of predefined permissions,
107 -- hard-coding the ids.
108
109 -- The first set of permissions is derived from the database, as loaded in a
110 -- loaded 1.6.1 database, plus a few changes previously applied in this upgrade
111 -- script.  The second set is derived from the IDL -- permissions that are referenced
112 -- in <permacrud> elements but not defined in the database.
113
114 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( -1, 'EVERYTHING',
115      '' );
116 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 1, 'OPAC_LOGIN',
117      'Allow a user to log in to the OPAC' );
118 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 2, 'STAFF_LOGIN',
119      'Allow a user to log in to the staff client' );
120 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 3, 'MR_HOLDS',
121      'Allow a user to create a metarecord holds' );
122 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 4, 'TITLE_HOLDS',
123      'Allow a user to place a hold at the title level' );
124 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 5, 'VOLUME_HOLDS',
125      'Allow a user to place a volume level hold' );
126 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 6, 'COPY_HOLDS',
127      'Allow a user to place a hold on a specific copy' );
128 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 7, 'REQUEST_HOLDS',
129      '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)' );
130 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 8, 'REQUEST_HOLDS_OVERRIDE',
131      '* no longer applicable' );
132 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 9, 'VIEW_HOLD',
133      'Allow a user to view another user''s holds' );
134 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 10, 'DELETE_HOLDS',
135      '* no longer applicable' );
136 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 11, 'UPDATE_HOLD',
137      'Allow a user to update another user''s hold' );
138 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 12, 'RENEW_CIRC',
139      'Allow a user to renew items' );
140 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 13, 'VIEW_USER_FINES_SUMMARY',
141      'Allow a user to view bill details' );
142 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 14, 'VIEW_USER_TRANSACTIONS',
143      'Allow a user to see another user''s grocery or circulation transactions in the Bills Interface; duplicate of VIEW_TRANSACTION' );
144 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 15, 'UPDATE_MARC',
145      'Allow a user to edit a MARC record' );
146 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 16, 'CREATE_MARC',
147      'Allow a user to create new MARC records' );
148 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 17, 'IMPORT_MARC',
149      'Allow a user to import a MARC record via the Z39.50 interface' );
150 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 18, 'CREATE_VOLUME',
151      'Allow a user to create a volume' );
152 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 19, 'UPDATE_VOLUME',
153      '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.' );
154 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 20, 'DELETE_VOLUME',
155      'Allow a user to delete a volume' );
156 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 21, 'CREATE_COPY',
157      'Allow a user to create a new copy object' );
158 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 22, 'UPDATE_COPY',
159      'Allow a user to edit a copy' );
160 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 23, 'DELETE_COPY',
161      'Allow a user to delete a copy' );
162 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 24, 'RENEW_HOLD_OVERRIDE',
163      'Allow a user to continue to renew an item even if it is required for a hold' );
164 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 25, 'CREATE_USER',
165      'Allow a user to create another user' );
166 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 26, 'UPDATE_USER',
167      'Allow a user to edit a user''s record' );
168 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 27, 'DELETE_USER',
169      'Allow a user to mark a user as deleted' );
170 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 28, 'VIEW_USER',
171      'Allow a user to view another user''s Patron Record' );
172 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 29, 'COPY_CHECKIN',
173      'Allow a user to check in a copy' );
174 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 30, 'CREATE_TRANSIT',
175      'Allow a user to place an item in transit' );
176 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 31, 'VIEW_PERMISSION',
177      'Allow a user to view user permissions within the user permissions editor' );
178 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 32, 'CHECKIN_BYPASS_HOLD_FULFILL',
179      '* no longer applicable' );
180 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 33, 'CREATE_PAYMENT',
181      'Allow a user to record payments in the Billing Interface' );
182 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 34, 'SET_CIRC_LOST',
183      'Allow a user to mark an item as ''lost''' );
184 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 35, 'SET_CIRC_MISSING',
185      'Allow a user to mark an item as ''missing''' );
186 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 36, 'SET_CIRC_CLAIMS_RETURNED',
187      'Allow a user to mark an item as ''claims returned''' );
188 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 37, 'CREATE_TRANSACTION',
189      'Allow a user to create a new billable transaction' );
190 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 38, 'VIEW_TRANSACTION',
191      'Allow a user may view another user''s transactions' );
192 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 39, 'CREATE_BILL',
193      'Allow a user to create a new bill on a transaction' );
194 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 40, 'VIEW_CONTAINER',
195      'Allow a user to view another user''s containers (buckets)' );
196 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 41, 'CREATE_CONTAINER',
197      'Allow a user to create a new container for another user' );
198 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 42, 'UPDATE_ORG_UNIT',
199      'Allow a user to change the settings for an organization unit' );
200 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 43, 'VIEW_CIRCULATIONS',
201      'Allow a user to see what another user has checked out' );
202 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 44, 'DELETE_CONTAINER',
203      'Allow a user to delete another user''s container' );
204 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 45, 'CREATE_CONTAINER_ITEM',
205      'Allow a user to create a container item for another user' );
206 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 46, 'CREATE_USER_GROUP_LINK',
207      'Allow a user to add other users to permission groups' );
208 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 47, 'REMOVE_USER_GROUP_LINK',
209      'Allow a user to remove other users from permission groups' );
210 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 48, 'VIEW_PERM_GROUPS',
211      'Allow a user to view other users'' permission groups' );
212 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 49, 'VIEW_PERMIT_CHECKOUT',
213      'Allow a user to determine whether another user can check out an item' );
214 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 50, 'UPDATE_BATCH_COPY',
215      'Allow a user to edit copies in batch' );
216 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 51, 'CREATE_PATRON_STAT_CAT',
217      'User may create a new patron statistical category' );
218 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 52, 'CREATE_COPY_STAT_CAT',
219      'User may create a copy statistical category' );
220 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 53, 'CREATE_PATRON_STAT_CAT_ENTRY',
221      'User may create an entry in a patron statistical category' );
222 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 54, 'CREATE_COPY_STAT_CAT_ENTRY',
223      'User may create an entry in a copy statistical category' );
224 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 55, 'UPDATE_PATRON_STAT_CAT',
225      'User may update a patron statistical category' );
226 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 56, 'UPDATE_COPY_STAT_CAT',
227      'User may update a copy statistical category' );
228 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 57, 'UPDATE_PATRON_STAT_CAT_ENTRY',
229      'User may update an entry in a patron statistical category' );
230 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 58, 'UPDATE_COPY_STAT_CAT_ENTRY',
231      'User may update an entry in a copy statistical category' );
232 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 59, 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
233      'User may link another user to an entry in a statistical category' );
234 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 60, 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
235      'User may link a copy to an entry in a statistical category' );
236 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 61, 'DELETE_PATRON_STAT_CAT',
237      'User may delete a patron statistical category' );
238 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 62, 'DELETE_COPY_STAT_CAT',
239      'User may delete a copy statistical category' );
240 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 63, 'DELETE_PATRON_STAT_CAT_ENTRY',
241      'User may delete an entry from a patron statistical category' );
242 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 64, 'DELETE_COPY_STAT_CAT_ENTRY',
243      'User may delete an entry from a copy statistical category' );
244 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 65, 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
245      'User may delete a patron statistical category entry map' );
246 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 66, 'DELETE_COPY_STAT_CAT_ENTRY_MAP',
247      'User may delete a copy statistical category entry map' );
248 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 67, 'CREATE_NON_CAT_TYPE',
249      'Allow a user to create a new non-cataloged item type' );
250 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 68, 'UPDATE_NON_CAT_TYPE',
251      'Allow a user to update a non-cataloged item type' );
252 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 69, 'CREATE_IN_HOUSE_USE',
253      'Allow a user to create a new in-house-use ' );
254 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 70, 'COPY_CHECKOUT',
255      'Allow a user to check out a copy' );
256 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 71, 'CREATE_COPY_LOCATION',
257      'Allow a user to create a new copy location' );
258 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 72, 'UPDATE_COPY_LOCATION',
259      'Allow a user to update a copy location' );
260 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 73, 'DELETE_COPY_LOCATION',
261      'Allow a user to delete a copy location' );
262 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 74, 'CREATE_COPY_TRANSIT',
263      'Allow a user to create a transit_copy object for transiting a copy' );
264 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 75, 'COPY_TRANSIT_RECEIVE',
265      'Allow a user to close out a transit on a copy' );
266 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 76, 'VIEW_HOLD_PERMIT',
267      'Allow a user to see if another user has permission to place a hold on a given copy' );
268 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 77, 'VIEW_COPY_CHECKOUT_HISTORY',
269      'Allow a user to view which users have checked out a given copy' );
270 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 78, 'REMOTE_Z3950_QUERY',
271      'Allow a user to perform Z39.50 queries against remote servers' );
272 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 79, 'REGISTER_WORKSTATION',
273      'Allow a user to register a new workstation' );
274 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 80, 'VIEW_COPY_NOTES',
275      'Allow a user to view all notes attached to a copy' );
276 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 81, 'VIEW_VOLUME_NOTES',
277      'Allow a user to view all notes attached to a volume' );
278 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 82, 'VIEW_TITLE_NOTES',
279      'Allow a user to view all notes attached to a title' );
280 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 83, 'CREATE_COPY_NOTE',
281      'Allow a user to create a new copy note' );
282 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 84, 'CREATE_VOLUME_NOTE',
283      'Allow a user to create a new volume note' );
284 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 85, 'CREATE_TITLE_NOTE',
285      'Allow a user to create a new title note' );
286 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 86, 'DELETE_COPY_NOTE',
287      'Allow a user to delete another user''s copy notes' );
288 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 87, 'DELETE_VOLUME_NOTE',
289      'Allow a user to delete another user''s volume note' );
290 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 88, 'DELETE_TITLE_NOTE',
291      'Allow a user to delete another user''s title note' );
292 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 89, 'UPDATE_CONTAINER',
293      'Allow a user to update another user''s container' );
294 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 90, 'CREATE_MY_CONTAINER',
295      'Allow a user to create a container for themselves' );
296 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 91, 'VIEW_HOLD_NOTIFICATION',
297      'Allow a user to view notifications attached to a hold' );
298 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 92, 'CREATE_HOLD_NOTIFICATION',
299      'Allow a user to create new hold notifications' );
300 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 93, 'UPDATE_ORG_SETTING',
301      'Allow a user to update an organization unit setting' );
302 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 94, 'OFFLINE_UPLOAD',
303      'Allow a user to upload an offline script' );
304 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 95, 'OFFLINE_VIEW',
305      'Allow a user to view uploaded offline script information' );
306 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 96, 'OFFLINE_EXECUTE',
307      'Allow a user to execute an offline script batch' );
308 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 97, 'CIRC_OVERRIDE_DUE_DATE',
309      'Allow a user to change the due date on an item to any date' );
310 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 98, 'CIRC_PERMIT_OVERRIDE',
311      'Allow a user to bypass the circulation permit call for check out' );
312 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 99, 'COPY_IS_REFERENCE.override',
313      'Allow a user to override the copy_is_reference event' );
314 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 100, 'VOID_BILLING',
315      'Allow a user to void a bill' );
316 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 101, 'CIRC_CLAIMS_RETURNED.override',
317      'Allow a user to check in or check out an item that has a status of ''claims returned''' );
318 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 102, 'COPY_BAD_STATUS.override',
319      'Allow a user to check out an item in a non-circulatable status' );
320 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 103, 'COPY_ALERT_MESSAGE.override',
321      'Allow a user to check in/out an item that has an alert message' );
322 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 104, 'COPY_STATUS_LOST.override',
323      'Allow a user to remove the lost status from a copy' );
324 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 105, 'COPY_STATUS_MISSING.override',
325      'Allow a user to change the missing status on a copy' );
326 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 106, 'ABORT_TRANSIT',
327      'Allow a user to abort a copy transit if the user is at the transit destination or source' );
328 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 107, 'ABORT_REMOTE_TRANSIT',
329      'Allow a user to abort a copy transit if the user is not at the transit source or dest' );
330 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 108, 'VIEW_ZIP_DATA',
331      'Allow a user to query the ZIP code data method' );
332 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 109, 'CANCEL_HOLDS',
333      'Allow a user to cancel holds' );
334 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 110, 'CREATE_DUPLICATE_HOLDS',
335      'Allow a user to create duplicate holds (two or more holds on the same title)' );
336 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 111, 'actor.org_unit.closed_date.delete',
337      'Allow a user to remove a closed date interval for a given location' );
338 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 112, 'actor.org_unit.closed_date.update',
339      'Allow a user to update a closed date interval for a given location' );
340 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 113, 'actor.org_unit.closed_date.create',
341      'Allow a user to create a new closed date for a location' );
342 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 114, 'DELETE_NON_CAT_TYPE',
343      'Allow a user to delete a non cataloged type' );
344 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 115, 'money.collections_tracker.create',
345      'Allow a user to put someone into collections' );
346 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 116, 'money.collections_tracker.delete',
347      'Allow a user to remove someone from collections' );
348 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 117, 'BAR_PATRON',
349      'Allow a user to bar a patron' );
350 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 118, 'UNBAR_PATRON',
351      'Allow a user to un-bar a patron' );
352 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 119, 'DELETE_WORKSTATION',
353      'Allow a user to remove an existing workstation so a new one can replace it' );
354 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 120, 'group_application.user',
355      'Allow a user to add/remove users to/from the "User" group' );
356 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 121, 'group_application.user.patron',
357      'Allow a user to add/remove users to/from the "Patron" group' );
358 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 122, 'group_application.user.staff',
359      'Allow a user to add/remove users to/from the "Staff" group' );
360 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 123, 'group_application.user.staff.circ',
361      'Allow a user to add/remove users to/from the "Circulator" group' );
362 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 124, 'group_application.user.staff.cat',
363      'Allow a user to add/remove users to/from the "Cataloger" group' );
364 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 125, 'group_application.user.staff.admin.global_admin',
365      'Allow a user to add/remove users to/from the "GlobalAdmin" group' );
366 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 126, 'group_application.user.staff.admin.local_admin',
367      'Allow a user to add/remove users to/from the "LocalAdmin" group' );
368 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 127, 'group_application.user.staff.admin.lib_manager',
369      'Allow a user to add/remove users to/from the "LibraryManager" group' );
370 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 128, 'group_application.user.staff.cat.cat1',
371      'Allow a user to add/remove users to/from the "Cat1" group' );
372 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 129, 'group_application.user.staff.supercat',
373      'Allow a user to add/remove users to/from the "Supercat" group' );
374 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 130, 'group_application.user.sip_client',
375      'Allow a user to add/remove users to/from the "SIP-Client" group' );
376 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 131, 'group_application.user.vendor',
377      'Allow a user to add/remove users to/from the "Vendor" group' );
378 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 132, 'ITEM_AGE_PROTECTED.override',
379      'Allow a user to place a hold on an age-protected item' );
380 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 133, 'MAX_RENEWALS_REACHED.override',
381      'Allow a user to renew an item past the maximum renewal count' );
382 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 134, 'PATRON_EXCEEDS_CHECKOUT_COUNT.override',
383      'Allow staff to override checkout count failure' );
384 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 135, 'PATRON_EXCEEDS_OVERDUE_COUNT.override',
385      'Allow staff to override overdue count failure' );
386 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 136, 'PATRON_EXCEEDS_FINES.override',
387      'Allow staff to override fine amount checkout failure' );
388 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 137, 'CIRC_EXCEEDS_COPY_RANGE.override',
389      'Allow staff to override circulation copy range failure' );
390 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 138, 'ITEM_ON_HOLDS_SHELF.override',
391      'Allow staff to override item on holds shelf failure' );
392 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 139, 'COPY_NOT_AVAILABLE.override',
393      'Allow staff to force checkout of Missing/Lost type items' );
394 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 140, 'HOLD_EXISTS.override',
395      'Allow a user to place multiple holds on a single title' );
396 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 141, 'RUN_REPORTS',
397      'Allow a user to run reports' );
398 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 142, 'SHARE_REPORT_FOLDER',
399      'Allow a user to share report his own folders' );
400 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 143, 'VIEW_REPORT_OUTPUT',
401      'Allow a user to view report output' );
402 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 144, 'COPY_CIRC_NOT_ALLOWED.override',
403      'Allow a user to checkout an item that is marked as non-circ' );
404 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 145, 'DELETE_CONTAINER_ITEM',
405      'Allow a user to delete an item out of another user''s container' );
406 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 146, 'ASSIGN_WORK_ORG_UNIT',
407      'Allow a staff member to define where another staff member has their permissions' );
408 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 147, 'CREATE_FUNDING_SOURCE',
409      'Allow a user to create a new funding source' );
410 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 148, 'DELETE_FUNDING_SOURCE',
411      'Allow a user to delete a funding source' );
412 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 149, 'VIEW_FUNDING_SOURCE',
413      'Allow a user to view a funding source' );
414 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 150, 'UPDATE_FUNDING_SOURCE',
415      'Allow a user to update a funding source' );
416 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 151, 'CREATE_FUND',
417      'Allow a user to create a new fund' );
418 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 152, 'DELETE_FUND',
419      'Allow a user to delete a fund' );
420 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 153, 'VIEW_FUND',
421      'Allow a user to view a fund' );
422 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 154, 'UPDATE_FUND',
423      'Allow a user to update a fund' );
424 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 155, 'CREATE_FUND_ALLOCATION',
425      'Allow a user to create a new fund allocation' );
426 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 156, 'DELETE_FUND_ALLOCATION',
427      'Allow a user to delete a fund allocation' );
428 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 157, 'VIEW_FUND_ALLOCATION',
429      'Allow a user to view a fund allocation' );
430 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 158, 'UPDATE_FUND_ALLOCATION',
431      'Allow a user to update a fund allocation' );
432 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 159, 'GENERAL_ACQ',
433      'Lowest level permission required to access the ACQ interface' );
434 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 160, 'CREATE_PROVIDER',
435      'Allow a user to create a new provider' );
436 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 161, 'DELETE_PROVIDER',
437      'Allow a user to delate a provider' );
438 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 162, 'VIEW_PROVIDER',
439      'Allow a user to view a provider' );
440 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 163, 'UPDATE_PROVIDER',
441      'Allow a user to update a provider' );
442 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 164, 'ADMIN_FUNDING_SOURCE',
443      'Allow a user to create/view/update/delete a funding source' );
444 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 165, 'ADMIN_FUND',
445      '(Deprecated) Allow a user to create/view/update/delete a fund' );
446 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 166, 'MANAGE_FUNDING_SOURCE',
447      'Allow a user to view/credit/debit a funding source' );
448 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 167, 'MANAGE_FUND',
449      'Allow a user to view/credit/debit a fund' );
450 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 168, 'CREATE_PICKLIST',
451      'Allows a user to create a picklist' );
452 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 169, 'ADMIN_PROVIDER',
453      'Allow a user to create/view/update/delete a provider' );
454 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 170, 'MANAGE_PROVIDER',
455      'Allow a user to view and purchase from a provider' );
456 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 171, 'VIEW_PICKLIST',
457      'Allow a user to view another users picklist' );
458 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 172, 'DELETE_RECORD',
459      'Allow a staff member to directly remove a bibliographic record' );
460 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 173, 'ADMIN_CURRENCY_TYPE',
461      'Allow a user to create/view/update/delete a currency_type' );
462 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 174, 'MARK_BAD_DEBT',
463      'Allow a user to mark a transaction as bad (unrecoverable) debt' );
464 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 175, 'VIEW_BILLING_TYPE',
465      'Allow a user to view billing types' );
466 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 176, 'MARK_ITEM_AVAILABLE',
467      'Allow a user to mark an item status as ''available''' );
468 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 177, 'MARK_ITEM_CHECKED_OUT',
469      'Allow a user to mark an item status as ''checked out''' );
470 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 178, 'MARK_ITEM_BINDERY',
471      'Allow a user to mark an item status as ''bindery''' );
472 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 179, 'MARK_ITEM_LOST',
473      'Allow a user to mark an item status as ''lost''' );
474 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 180, 'MARK_ITEM_MISSING',
475      'Allow a user to mark an item status as ''missing''' );
476 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 181, 'MARK_ITEM_IN_PROCESS',
477      'Allow a user to mark an item status as ''in process''' );
478 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 182, 'MARK_ITEM_IN_TRANSIT',
479      'Allow a user to mark an item status as ''in transit''' );
480 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 183, 'MARK_ITEM_RESHELVING',
481      'Allow a user to mark an item status as ''reshelving''' );
482 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 184, 'MARK_ITEM_ON_HOLDS_SHELF',
483      'Allow a user to mark an item status as ''on holds shelf''' );
484 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 185, 'MARK_ITEM_ON_ORDER',
485      'Allow a user to mark an item status as ''on order''' );
486 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 186, 'MARK_ITEM_ILL',
487      'Allow a user to mark an item status as ''inter-library loan''' );
488 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 187, 'group_application.user.staff.acq',
489      'Allows a user to add/remove/edit users in the "ACQ" group' );
490 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 188, 'CREATE_PURCHASE_ORDER',
491      'Allows a user to create a purchase order' );
492 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 189, 'VIEW_PURCHASE_ORDER',
493      'Allows a user to view a purchase order' );
494 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 190, 'IMPORT_ACQ_LINEITEM_BIB_RECORD',
495      'Allows a user to import a bib record from the acq staging area (on-order record) into the ILS bib data set' );
496 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 191, 'RECEIVE_PURCHASE_ORDER',
497      'Allows a user to mark a purchase order, lineitem, or individual copy as received' );
498 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 192, 'VIEW_ORG_SETTINGS',
499      'Allows a user to view all org settings at the specified level' );
500 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 193, 'CREATE_MFHD_RECORD',
501      'Allows a user to create a new MFHD record' );
502 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 194, 'UPDATE_MFHD_RECORD',
503      'Allows a user to update an MFHD record' );
504 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 195, 'DELETE_MFHD_RECORD',
505      'Allows a user to delete an MFHD record' );
506 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 196, 'ADMIN_ACQ_FUND',
507      'Allow a user to create/view/update/delete a fund' );
508 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 197, 'group_application.user.staff.acq_admin',
509      'Allows a user to add/remove/edit users in the "Acquisitions Administrators" group' );
510 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 198, 'SET_CIRC_CLAIMS_RETURNED.override',
511      'Allows staff to override the max claims returned value for a patron' );
512 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 199, 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
513      'Allows staff to manually change a patron''s claims returned count' );
514 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 200, 'UPDATE_BILL_NOTE',
515      'Allows staff to edit the note for a bill on a transaction' );
516 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 201, 'UPDATE_PAYMENT_NOTE',
517      'Allows staff to edit the note for a payment on a transaction' );
518 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 202, 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
519      'Allows staff to manually change a patron''s claims never checkout out count' );
520 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 203, 'ADMIN_COPY_LOCATION_ORDER',
521      'Allow a user to create/view/update/delete a copy location order' );
522 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 204, 'ASSIGN_GROUP_PERM',
523      '' );
524 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 205, 'CREATE_AUDIENCE',
525      '' );
526 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 206, 'CREATE_BIB_LEVEL',
527      '' );
528 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 207, 'CREATE_CIRC_DURATION',
529      '' );
530 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 208, 'CREATE_CIRC_MOD',
531      '' );
532 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 209, 'CREATE_COPY_STATUS',
533      '' );
534 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 210, 'CREATE_HOURS_OF_OPERATION',
535      '' );
536 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 211, 'CREATE_ITEM_FORM',
537      '' );
538 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 212, 'CREATE_ITEM_TYPE',
539      '' );
540 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 213, 'CREATE_LANGUAGE',
541      '' );
542 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 214, 'CREATE_LASSO',
543      '' );
544 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 215, 'CREATE_LASSO_MAP',
545      '' );
546 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 216, 'CREATE_LIT_FORM',
547      '' );
548 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 217, 'CREATE_METABIB_FIELD',
549      '' );
550 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 218, 'CREATE_NET_ACCESS_LEVEL',
551      '' );
552 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 219, 'CREATE_ORG_ADDRESS',
553      '' );
554 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 220, 'CREATE_ORG_TYPE',
555      '' );
556 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 221, 'CREATE_ORG_UNIT',
557      '' );
558 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 222, 'CREATE_ORG_UNIT_CLOSING',
559      '' );
560 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 223, 'CREATE_PERM',
561      '' );
562 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 224, 'CREATE_RELEVANCE_ADJUSTMENT',
563      '' );
564 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 225, 'CREATE_SURVEY',
565      '' );
566 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 226, 'CREATE_VR_FORMAT',
567      '' );
568 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 227, 'CREATE_XML_TRANSFORM',
569      '' );
570 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 228, 'DELETE_AUDIENCE',
571      '' );
572 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 229, 'DELETE_BIB_LEVEL',
573      '' );
574 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 230, 'DELETE_CIRC_DURATION',
575      '' );
576 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 231, 'DELETE_CIRC_MOD',
577      '' );
578 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 232, 'DELETE_COPY_STATUS',
579      '' );
580 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 233, 'DELETE_HOURS_OF_OPERATION',
581      '' );
582 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 234, 'DELETE_ITEM_FORM',
583      '' );
584 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 235, 'DELETE_ITEM_TYPE',
585      '' );
586 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 236, 'DELETE_LANGUAGE',
587      '' );
588 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 237, 'DELETE_LASSO',
589      '' );
590 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 238, 'DELETE_LASSO_MAP',
591      '' );
592 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 239, 'DELETE_LIT_FORM',
593      '' );
594 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 240, 'DELETE_METABIB_FIELD',
595      '' );
596 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 241, 'DELETE_NET_ACCESS_LEVEL',
597      '' );
598 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 242, 'DELETE_ORG_ADDRESS',
599      '' );
600 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 243, 'DELETE_ORG_TYPE',
601      '' );
602 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 244, 'DELETE_ORG_UNIT',
603      '' );
604 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 245, 'DELETE_ORG_UNIT_CLOSING',
605      '' );
606 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 246, 'DELETE_PERM',
607      '' );
608 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 247, 'DELETE_RELEVANCE_ADJUSTMENT',
609      '' );
610 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 248, 'DELETE_SURVEY',
611      '' );
612 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 249, 'DELETE_TRANSIT',
613      '' );
614 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 250, 'DELETE_VR_FORMAT',
615      '' );
616 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 251, 'DELETE_XML_TRANSFORM',
617      '' );
618 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 252, 'REMOVE_GROUP_PERM',
619      '' );
620 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 253, 'TRANSIT_COPY',
621      '' );
622 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 254, 'UPDATE_AUDIENCE',
623      '' );
624 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 255, 'UPDATE_BIB_LEVEL',
625      '' );
626 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 256, 'UPDATE_CIRC_DURATION',
627      '' );
628 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 257, 'UPDATE_CIRC_MOD',
629      '' );
630 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 258, 'UPDATE_COPY_NOTE',
631      '' );
632 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 259, 'UPDATE_COPY_STATUS',
633      '' );
634 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 260, 'UPDATE_GROUP_PERM',
635      '' );
636 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 261, 'UPDATE_HOURS_OF_OPERATION',
637      '' );
638 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 262, 'UPDATE_ITEM_FORM',
639      '' );
640 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 263, 'UPDATE_ITEM_TYPE',
641      '' );
642 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 264, 'UPDATE_LANGUAGE',
643      '' );
644 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 265, 'UPDATE_LASSO',
645      '' );
646 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 266, 'UPDATE_LASSO_MAP',
647      '' );
648 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 267, 'UPDATE_LIT_FORM',
649      '' );
650 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 268, 'UPDATE_METABIB_FIELD',
651      '' );
652 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 269, 'UPDATE_NET_ACCESS_LEVEL',
653      '' );
654 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 270, 'UPDATE_ORG_ADDRESS',
655      '' );
656 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 271, 'UPDATE_ORG_TYPE',
657      '' );
658 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 272, 'UPDATE_ORG_UNIT_CLOSING',
659      '' );
660 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 273, 'UPDATE_PERM',
661      '' );
662 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 274, 'UPDATE_RELEVANCE_ADJUSTMENT',
663      '' );
664 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 275, 'UPDATE_SURVEY',
665      '' );
666 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 276, 'UPDATE_TRANSIT',
667      '' );
668 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 277, 'UPDATE_VOLUME_NOTE',
669      '' );
670 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 278, 'UPDATE_VR_FORMAT',
671      '' );
672 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 279, 'UPDATE_XML_TRANSFORM',
673      '' );
674 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 280, 'MERGE_BIB_RECORDS',
675      '' );
676 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 281, 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
677      '' );
678 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 282, 'CREATE_ACQ_FUNDING_SOURCE',
679      '' );
680 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 283, 'CREATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
681      '' );
682 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 284, 'CREATE_AUTHORITY_IMPORT_QUEUE',
683      '' );
684 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 285, 'CREATE_AUTHORITY_RECORD_NOTE',
685      '' );
686 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 286, 'CREATE_BIB_IMPORT_FIELD_DEF',
687      '' );
688 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 287, 'CREATE_BIB_IMPORT_QUEUE',
689      '' );
690 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 288, 'CREATE_LOCALE',
691      '' );
692 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 289, 'CREATE_MARC_CODE',
693      '' );
694 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 290, 'CREATE_TRANSLATION',
695      '' );
696 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 291, 'DELETE_ACQ_FUNDING_SOURCE',
697      '' );
698 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 292, 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
699      '' );
700 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 293, 'DELETE_AUTHORITY_IMPORT_QUEUE',
701      '' );
702 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 294, 'DELETE_AUTHORITY_RECORD_NOTE',
703      '' );
704 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 295, 'DELETE_BIB_IMPORT_IMPORT_FIELD_DEF',
705      '' );
706 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 296, 'DELETE_BIB_IMPORT_QUEUE',
707      '' );
708 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 297, 'DELETE_LOCALE',
709      '' );
710 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 298, 'DELETE_MARC_CODE',
711      '' );
712 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 299, 'DELETE_TRANSLATION',
713      '' );
714 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 300, 'UPDATE_ACQ_FUNDING_SOURCE',
715      '' );
716 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 301, 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
717      '' );
718 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 302, 'UPDATE_AUTHORITY_IMPORT_QUEUE',
719      '' );
720 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 303, 'UPDATE_AUTHORITY_RECORD_NOTE',
721      '' );
722 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 304, 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
723      '' );
724 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 305, 'UPDATE_BIB_IMPORT_QUEUE',
725      '' );
726 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 306, 'UPDATE_LOCALE',
727      '' );
728 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 307, 'UPDATE_MARC_CODE',
729      '' );
730 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 308, 'UPDATE_TRANSLATION',
731      '' );
732 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 309, 'VIEW_ACQ_FUNDING_SOURCE',
733      '' );
734 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 310, 'VIEW_AUTHORITY_RECORD_NOTES',
735      '' );
736 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 311, 'CREATE_IMPORT_ITEM',
737      '' );
738 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 312, 'CREATE_IMPORT_ITEM_ATTR_DEF',
739      '' );
740 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 313, 'CREATE_IMPORT_TRASH_FIELD',
741      '' );
742 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 314, 'DELETE_IMPORT_ITEM',
743      '' );
744 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 315, 'DELETE_IMPORT_ITEM_ATTR_DEF',
745      '' );
746 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 316, 'DELETE_IMPORT_TRASH_FIELD',
747      '' );
748 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 317, 'UPDATE_IMPORT_ITEM',
749      '' );
750 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 318, 'UPDATE_IMPORT_ITEM_ATTR_DEF',
751      '' );
752 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 319, 'UPDATE_IMPORT_TRASH_FIELD',
753      '' );
754 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 320, 'UPDATE_ORG_UNIT_SETTING_ALL',
755      '' );
756 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 321, 'UPDATE_ORG_UNIT_SETTING.circ.lost_materials_processing_fee',
757      '' );
758 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 322, 'UPDATE_ORG_UNIT_SETTING.cat.default_item_price',
759      '' );
760 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 323, 'UPDATE_ORG_UNIT_SETTING.auth.opac_timeout',
761      '' );
762 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 324, 'UPDATE_ORG_UNIT_SETTING.auth.staff_timeout',
763      '' );
764 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 325, 'UPDATE_ORG_UNIT_SETTING.org.bounced_emails',
765      '' );
766 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 326, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_alert_interval',
767      '' );
768 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 327, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_interval',
769      '' );
770 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 328, 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow',
771      '' );
772 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 329, 'UPDATE_ORG_UNIT_SETTING.circ.void_overdue_on_lost',
773      '' );
774 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 330, 'UPDATE_ORG_UNIT_SETTING.circ.hold_stalling.soft',
775      '' );
776 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 331, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.hard',
777      '' );
778 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 332, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.soft',
779      '' );
780 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 333, 'UPDATE_ORG_UNIT_SETTING.opac.barcode_regex',
781      '' );
782 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 334, 'UPDATE_ORG_UNIT_SETTING.global.password_regex',
783      '' );
784 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 335, 'UPDATE_ORG_UNIT_SETTING.circ.item_checkout_history.max',
785      '' );
786 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 336, 'UPDATE_ORG_UNIT_SETTING.circ.reshelving_complete.interval',
787      '' );
788 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 337, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.patron_login_timeout',
789      '' );
790 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 338, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.alert_on_checkout_event',
791      '' );
792 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 339, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.require_patron_password',
793      '' );
794 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 340, 'UPDATE_ORG_UNIT_SETTING.global.juvenile_age_threshold',
795      '' );
796 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 341, 'UPDATE_ORG_UNIT_SETTING.cat.bib.keep_on_empty',
797      '' );
798 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 342, 'UPDATE_ORG_UNIT_SETTING.cat.bib.alert_on_empty',
799      '' );
800 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 343, 'UPDATE_ORG_UNIT_SETTING.patron.password.use_phone',
801      '' );
802 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 344, 'HOLD_ITEM_CHECKED_OUT.override',
803      'Allows a user to place a hold on an item that they already have checked out' );
804 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 345, 'ADMIN_ACQ_CANCEL_CAUSE',
805      'Allow a user to create/update/delete reasons for order cancellations' );
806 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 346, 'ACQ_XFER_MANUAL_DFUND_AMOUNT',
807      'Allow a user to transfer different amounts of money out of one fund and into another' );
808 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 347, 'OVERRIDE_HOLD_HAS_LOCAL_COPY',
809      'Allow a user to override the circ.holds.hold_has_copy_at.block setting' );
810 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 348, 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
811      'Allow a user to change the pickup and transit destination for a captured hold item already in transit' );
812 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 349, 'COPY_NEEDED_FOR_HOLD.override',
813      'Allow a user to force renewal of an item that could fulfill a hold request' );
814 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 350, 'MERGE_AUTH_RECORDS',
815      'Allow a user to merge authority records together' );
816 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 351, 'ALLOW_ALT_TCN',
817      'Allows staff to import a record using an alternate TCN to avoid conflicts' );
818 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 352, 'ADMIN_TRIGGER_EVENT_DEF',
819      'Allow a user to administer trigger event definitions' );
820 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 353, 'ADMIN_TRIGGER_CLEANUP',
821      'Allow a user to create, delete, and update trigger cleanup entries' );
822 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 354, 'CREATE_TRIGGER_CLEANUP',
823      'Allow a user to create trigger cleanup entries' );
824 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 355, 'DELETE_TRIGGER_CLEANUP',
825      'Allow a user to delete trigger cleanup entries' );
826 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 356, 'UPDATE_TRIGGER_CLEANUP',
827      'Allow a user to update trigger cleanup entries' );
828 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 357, 'CREATE_TRIGGER_EVENT_DEF',
829      'Allow a user to create trigger event definitions' );
830 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 358, 'DELETE_TRIGGER_EVENT_DEF',
831      'Allow a user to delete trigger event definitions' );
832 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 359, 'UPDATE_TRIGGER_EVENT_DEF',
833      'Allow a user to update trigger event definitions' );
834 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 360, 'VIEW_TRIGGER_EVENT_DEF',
835      'Allow a user to view trigger event definitions' );
836 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 361, 'ADMIN_TRIGGER_HOOK',
837      'Allow a user to create, update, and delete trigger hooks' );
838 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 362, 'CREATE_TRIGGER_HOOK',
839      'Allow a user to create trigger hooks' );
840 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 363, 'DELETE_TRIGGER_HOOK',
841      'Allow a user to delete trigger hooks' );
842 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 364, 'UPDATE_TRIGGER_HOOK',
843      'Allow a user to update trigger hooks' );
844 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 365, 'ADMIN_TRIGGER_REACTOR',
845      'Allow a user to create, update, and delete trigger reactors' );
846 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 366, 'CREATE_TRIGGER_REACTOR',
847      'Allow a user to create trigger reactors' );
848 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 367, 'DELETE_TRIGGER_REACTOR',
849      'Allow a user to delete trigger reactors' );
850 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 368, 'UPDATE_TRIGGER_REACTOR',
851      'Allow a user to update trigger reactors' );
852 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 369, 'ADMIN_TRIGGER_TEMPLATE_OUTPUT',
853      'Allow a user to delete trigger template output' );
854 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 370, 'DELETE_TRIGGER_TEMPLATE_OUTPUT',
855      'Allow a user to delete trigger template output' );
856 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 371, 'ADMIN_TRIGGER_VALIDATOR',
857      'Allow a user to create, update, and delete trigger validators' );
858 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 372, 'CREATE_TRIGGER_VALIDATOR',
859      'Allow a user to create trigger validators' );
860 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 373, 'DELETE_TRIGGER_VALIDATOR',
861      'Allow a user to delete trigger validators' );
862 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 374, 'UPDATE_TRIGGER_VALIDATOR',
863      'Allow a user to update trigger validators' );
864 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 375, 'HOLD_LOCAL_AVAIL_OVERRIDE',
865      'Allow a user to place a hold despite the availability of a local copy' );
866 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 376, 'ADMIN_BOOKING_RESOURCE',
867      'Enables the user to create/update/delete booking resources' );
868 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 377, 'ADMIN_BOOKING_RESOURCE_TYPE',
869      'Enables the user to create/update/delete booking resource types' );
870 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 378, 'ADMIN_BOOKING_RESOURCE_ATTR',
871      'Enables the user to create/update/delete booking resource attributes' );
872 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 379, 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
873      'Enables the user to create/update/delete booking resource attribute maps' );
874 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 380, 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
875      'Enables the user to create/update/delete booking resource attribute values' );
876 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 381, 'ADMIN_BOOKING_RESERVATION',
877      'Enables the user to create/update/delete booking reservations' );
878 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 382, 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
879      'Enables the user to create/update/delete booking reservation attribute value maps' );
880 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 383, 'RETRIEVE_RESERVATION_PULL_LIST',
881      'Allows a user to retrieve a booking reservation pull list' );
882 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 384, 'CAPTURE_RESERVATION',
883      'Allows a user to capture booking reservations' );
884 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 385, 'UPDATE_RECORD',
885      '' );
886 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 386, 'UPDATE_ORG_UNIT_SETTING.circ.block_renews_for_holds',
887      '' );
888 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 387, 'MERGE_USERS',
889      'Allows user records to be merged' );
890 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 388, 'ISSUANCE_HOLDS',
891      'Allow a user to place holds on serials issuances' );
892 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 389, 'VIEW_CREDIT_CARD_PROCESSING',
893      'View org unit settings related to credit card processing' );
894 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 390, 'ADMIN_CREDIT_CARD_PROCESSING',
895      'Update org unit settings related to credit card processing' );
896 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 391, 'ADMIN_SERIAL_CAPTION_PATTERN',
897         'Create/update/delete serial caption and pattern objects' );
898 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 392, 'ADMIN_SERIAL_SUBSCRIPTION',
899         'Create/update/delete serial subscription objects' );
900 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 393, 'ADMIN_SERIAL_DISTRIBUTION',
901         'Create/update/delete serial distribution objects' );
902 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 394, 'ADMIN_SERIAL_STREAM',
903         'Create/update/delete serial stream objects' );
904 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 395, 'RECEIVE_SERIAL',
905         'Receive serial items' );
906
907 -- Now for the permissions from the IDL.  We don't have descriptions for them.
908
909 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 396, 'ADMIN_ACQ_CLAIM' );
910 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 397, 'ADMIN_ACQ_CLAIM_EVENT_TYPE' );
911 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 398, 'ADMIN_ACQ_CLAIM_TYPE' );
912 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 399, 'ADMIN_ACQ_DISTRIB_FORMULA' );
913 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 400, 'ADMIN_ACQ_FISCAL_YEAR' );
914 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 401, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT' );
915 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 402, 'ADMIN_ACQ_FUND_TAG' );
916 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 403, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT' );
917 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 404, 'ADMIN_AGE_PROTECT_RULE' );
918 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 405, 'ADMIN_ASSET_COPY_TEMPLATE' );
919 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 406, 'ADMIN_BOOKING_RESERVATION_ATTR_MAP' );
920 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 407, 'ADMIN_CIRC_MATRIX_MATCHPOINT' );
921 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 408, 'ADMIN_CIRC_MOD' );
922 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 409, 'ADMIN_CLAIM_POLICY' );
923 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 410, 'ADMIN_CONFIG_REMOTE_ACCOUNT' );
924 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 411, 'ADMIN_FIELD_DOC' );
925 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 412, 'ADMIN_GLOBAL_FLAG' );
926 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 413, 'ADMIN_GROUP_PENALTY_THRESHOLD' );
927 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 414, 'ADMIN_HOLD_CANCEL_CAUSE' );
928 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 415, 'ADMIN_HOLD_MATRIX_MATCHPOINT' );
929 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 416, 'ADMIN_IDENT_TYPE' );
930 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 417, 'ADMIN_IMPORT_ITEM_ATTR_DEF' );
931 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 418, 'ADMIN_INDEX_NORMALIZER' );
932 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 419, 'ADMIN_INVOICE' );
933 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 420, 'ADMIN_INVOICE_METHOD' );
934 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 421, 'ADMIN_INVOICE_PAYMENT_METHOD' );
935 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 422, 'ADMIN_LINEITEM_MARC_ATTR_DEF' );
936 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 423, 'ADMIN_MARC_CODE' );
937 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 424, 'ADMIN_MAX_FINE_RULE' );
938 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 425, 'ADMIN_MERGE_PROFILE' );
939 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 426, 'ADMIN_ORG_UNIT_SETTING_TYPE' );
940 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 427, 'ADMIN_RECURRING_FINE_RULE' );
941 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 428, 'ADMIN_STANDING_PENALTY' );
942 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 429, 'ADMIN_SURVEY' );
943 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 430, 'ADMIN_USER_REQUEST_TYPE' );
944 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 431, 'ADMIN_USER_SETTING_GROUP' );
945 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 432, 'ADMIN_USER_SETTING_TYPE' );
946 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 433, 'ADMIN_Z3950_SOURCE' );
947 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 434, 'CREATE_BIB_BTYPE' );
948 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 435, 'CREATE_BIBLIO_FINGERPRINT' );
949 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 436, 'CREATE_BIB_SOURCE' );
950 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 437, 'CREATE_BILLING_TYPE' );
951 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 438, 'CREATE_CN_BTYPE' );
952 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 439, 'CREATE_COPY_BTYPE' );
953 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 440, 'CREATE_INVOICE' );
954 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 441, 'CREATE_INVOICE_ITEM_TYPE' );
955 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 442, 'CREATE_INVOICE_METHOD' );
956 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 443, 'CREATE_MERGE_PROFILE' );
957 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 444, 'CREATE_METABIB_CLASS' );
958 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 445, 'CREATE_METABIB_SEARCH_ALIAS' );
959 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 446, 'CREATE_USER_BTYPE' );
960 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 447, 'DELETE_BIB_BTYPE' );
961 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 448, 'DELETE_BIBLIO_FINGERPRINT' );
962 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 449, 'DELETE_BIB_SOURCE' );
963 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 450, 'DELETE_BILLING_TYPE' );
964 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 451, 'DELETE_CN_BTYPE' );
965 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 452, 'DELETE_COPY_BTYPE' );
966 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 453, 'DELETE_INVOICE_ITEM_TYPE' );
967 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 454, 'DELETE_INVOICE_METHOD' );
968 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 455, 'DELETE_MERGE_PROFILE' );
969 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 456, 'DELETE_METABIB_CLASS' );
970 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 457, 'DELETE_METABIB_SEARCH_ALIAS' );
971 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 458, 'DELETE_USER_BTYPE' );
972 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 459, 'MANAGE_CLAIM' );
973 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 460, 'UPDATE_BIB_BTYPE' );
974 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 461, 'UPDATE_BIBLIO_FINGERPRINT' );
975 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 462, 'UPDATE_BIB_SOURCE' );
976 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 463, 'UPDATE_BILLING_TYPE' );
977 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 464, 'UPDATE_CN_BTYPE' );
978 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 465, 'UPDATE_COPY_BTYPE' );
979 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 466, 'UPDATE_INVOICE_ITEM_TYPE' );
980 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 467, 'UPDATE_INVOICE_METHOD' );
981 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 468, 'UPDATE_MERGE_PROFILE' );
982 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 469, 'UPDATE_METABIB_CLASS' );
983 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 470, 'UPDATE_METABIB_SEARCH_ALIAS' );
984 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 471, 'UPDATE_USER_BTYPE' );
985 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 472, 'user_request.create' );
986 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 473, 'user_request.delete' );
987 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 474, 'user_request.update' );
988 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 475, 'user_request.view' );
989 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 476, 'VIEW_ACQ_FUND_ALLOCATION_PERCENT' );
990 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 477, 'VIEW_CIRC_MATRIX_MATCHPOINT' );
991 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 478, 'VIEW_CLAIM' );
992 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 479, 'VIEW_GROUP_PENALTY_THRESHOLD' );
993 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 480, 'VIEW_HOLD_MATRIX_MATCHPOINT' );
994 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 481, 'VIEW_INVOICE' );
995 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 482, 'VIEW_MERGE_PROFILE' );
996 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 483, 'VIEW_SERIAL_SUBSCRIPTION' );
997 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 484, 'VIEW_STANDING_PENALTY' );
998
999 -- For every permission in the temp_perm table that has a matching
1000 -- permission in the real table: record the original id.
1001
1002 UPDATE permission.temp_perm AS tp
1003 SET old_id =
1004         (
1005                 SELECT id
1006                 FROM permission.perm_list AS ppl
1007                 WHERE ppl.code = tp.code
1008         )
1009 WHERE code IN ( SELECT code FROM permission.perm_list );
1010
1011 -- Start juggling ids.
1012
1013 -- If any permissions have negative ids (with the special exception of -1),
1014 -- we need to move them into the positive range in order to avoid duplicate
1015 -- key problems (since we are going to use the negative range as a temporary
1016 -- staging area).
1017
1018 -- First, move any predefined permissions that have negative ids (again with
1019 -- the special exception of -1).  Temporarily give them positive ids based on
1020 -- the sequence.
1021
1022 UPDATE permission.perm_list
1023 SET id = NEXTVAL('permission.perm_list_id_seq'::regclass)
1024 WHERE id < -1
1025   AND code IN (SELECT code FROM permission.temp_perm);
1026
1027 -- Identify any non-predefined permissions whose ids are either negative
1028 -- or within the range (0-1000) reserved for predefined permissions.
1029 -- Assign them ids above 1000, based on the sequence.  Record the new
1030 -- ids in the temp_perm table.
1031
1032 INSERT INTO permission.temp_perm ( id, code, description, old_id, predefined )
1033 (
1034         SELECT NEXTVAL('permission.perm_list_id_seq'::regclass),
1035                 code, description, id, false
1036         FROM permission.perm_list
1037         WHERE  ( id < -1 OR id BETWEEN 0 AND 1000 )
1038         AND code NOT IN (SELECT code FROM permission.temp_perm)
1039 );
1040
1041 -- Now update the ids of those non-predefined permissions, using the
1042 -- values assigned in the previous step.
1043
1044 UPDATE permission.perm_list AS ppl
1045 SET id = (
1046                 SELECT id
1047                 FROM permission.temp_perm AS tp
1048                 WHERE tp.code = ppl.code
1049         )
1050 WHERE id IN ( SELECT old_id FROM permission.temp_perm WHERE NOT predefined );
1051
1052 -- Now the negative ids have been eliminated, except for -1.  Move all the
1053 -- predefined permissions temporarily into the negative range.
1054
1055 UPDATE permission.perm_list
1056 SET id = -1 - id
1057 WHERE id <> -1
1058 AND code IN ( SELECT code from permission.temp_perm WHERE predefined );
1059
1060 -- Apply the final ids to the existing predefined permissions.
1061
1062 UPDATE permission.perm_list AS ppl
1063 SET id =
1064         (
1065                 SELECT id
1066                 FROM permission.temp_perm AS tp
1067                 WHERE tp.code = ppl.code
1068         )
1069 WHERE
1070         id <> -1
1071         AND ppl.code IN
1072         (
1073                 SELECT code from permission.temp_perm
1074                 WHERE predefined
1075                 AND old_id IS NOT NULL
1076         );
1077
1078 -- If there are any predefined permissions that don't exist yet in
1079 -- permission.perm_list, insert them now.
1080
1081 INSERT INTO permission.perm_list ( id, code, description )
1082 (
1083         SELECT id, code, description
1084         FROM permission.temp_perm
1085         WHERE old_id IS NULL
1086 );
1087
1088 -- Reset the sequence to the lowest feasible value.  This may or may not
1089 -- accomplish anything, but it will do no harm.
1090
1091 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, GREATEST( 
1092         (SELECT MAX(id) FROM permission.perm_list), 1000 ));
1093
1094 -- If any permission lacks a description, use the code as a description.
1095 -- It's better than nothing.
1096
1097 UPDATE permission.perm_list
1098 SET description = code
1099 WHERE description IS NULL
1100    OR description = '';
1101
1102 -- Thus endeth the Great Renumbering.
1103
1104 -- Having massaged the permissions, massage the way they are assigned, by inserting
1105 -- rows into permission.grp_perm_map.  Some of these permissions may have already
1106 -- been assigned, so we insert the rows only if they aren't already there.
1107
1108 -- for backwards compat, give everyone the permission
1109 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1110     SELECT 1, id, 0, false FROM permission.perm_list AS perm
1111         WHERE code = 'HOLD_ITEM_CHECKED_OUT.override'
1112                 AND NOT EXISTS (
1113                         SELECT 1
1114                         FROM permission.grp_perm_map AS map
1115                         WHERE
1116                                 grp = 1
1117                                 AND map.perm = perm.id
1118                 );
1119
1120 -- Add trigger administration permissions to the Local System Administrator group.
1121 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1122     SELECT 10, id, 1, false FROM permission.perm_list AS perm
1123     WHERE (
1124                 perm.code LIKE 'ADMIN_TRIGGER%'
1125         OR perm.code LIKE 'CREATE_TRIGGER%'
1126         OR perm.code LIKE 'DELETE_TRIGGER%'
1127         OR perm.code LIKE 'UPDATE_TRIGGER%'
1128         ) AND NOT EXISTS (
1129                 SELECT 1
1130                 FROM permission.grp_perm_map AS map
1131                 WHERE
1132                         grp = 10
1133                         AND map.perm = perm.id
1134         );
1135
1136 -- View trigger permissions are required at a consortial level for initial setup
1137 -- (as before, only if the row doesn't already exist)
1138 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1139     SELECT 10, id, 0, false FROM permission.perm_list AS perm
1140         WHERE code LIKE 'VIEW_TRIGGER%'
1141                 AND NOT EXISTS (
1142                         SELECT 1
1143                         FROM permission.grp_perm_map AS map
1144                         WHERE
1145                                 grp = 10
1146                                 AND map.perm = perm.id
1147                 );
1148
1149 -- Permission for merging auth records may already be defined,
1150 -- so add it only if it isn't there.
1151 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1152     SELECT 4, id, 1, false FROM permission.perm_list AS perm
1153         WHERE code = 'MERGE_AUTH_RECORDS'
1154                 AND NOT EXISTS (
1155                         SELECT 1
1156                         FROM permission.grp_perm_map AS map
1157                         WHERE
1158                                 grp = 4
1159                                 AND map.perm = perm.id
1160                 );
1161
1162 -- Create a reference table as parent to both
1163 -- config.org_unit_setting_type and config_usr_setting_type
1164
1165 CREATE TABLE config.settings_group (
1166     name    TEXT PRIMARY KEY,
1167     label   TEXT UNIQUE NOT NULL -- I18N
1168 );
1169
1170 -- org_unit setting types
1171 CREATE TABLE config.org_unit_setting_type (
1172     name            TEXT    PRIMARY KEY,
1173     label           TEXT    UNIQUE NOT NULL,
1174     grp             TEXT    REFERENCES config.settings_group (name),
1175     description     TEXT,
1176     datatype        TEXT    NOT NULL DEFAULT 'string',
1177     fm_class        TEXT,
1178     view_perm       INT,
1179     update_perm     INT,
1180     --
1181     -- define valid datatypes
1182     --
1183     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1184     ( 'bool', 'integer', 'float', 'currency', 'interval',
1185       'date', 'string', 'object', 'array', 'link' ) ),
1186     --
1187     -- fm_class is meaningful only for 'link' datatype
1188     --
1189     CONSTRAINT coust_no_empty_link CHECK
1190     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
1191       ( datatype <> 'link' AND fm_class IS NULL ) ),
1192         CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id)
1193                 ON UPDATE CASCADE
1194                 ON DELETE RESTRICT
1195                 DEFERRABLE INITIALLY DEFERRED,
1196         CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id)
1197                 ON UPDATE CASCADE
1198                 DEFERRABLE INITIALLY DEFERRED
1199 );
1200
1201 CREATE TABLE config.usr_setting_type (
1202
1203     name TEXT PRIMARY KEY,
1204     opac_visible BOOL NOT NULL DEFAULT FALSE,
1205     label TEXT UNIQUE NOT NULL,
1206     description TEXT,
1207     grp             TEXT    REFERENCES config.settings_group (name),
1208     datatype TEXT NOT NULL DEFAULT 'string',
1209     fm_class TEXT,
1210
1211     --
1212     -- define valid datatypes
1213     --
1214     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1215     ( 'bool', 'integer', 'float', 'currency', 'interval',
1216         'date', 'string', 'object', 'array', 'link' ) ),
1217
1218     --
1219     -- fm_class is meaningful only for 'link' datatype
1220     --
1221     CONSTRAINT coust_no_empty_link CHECK
1222     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
1223         ( datatype <> 'link' AND fm_class IS NULL ) )
1224
1225 );
1226
1227 --------------------------------------
1228 -- Seed data for org_unit_setting_type
1229 --------------------------------------
1230
1231 INSERT into config.org_unit_setting_type
1232 ( name, label, description, datatype ) VALUES
1233
1234 ( 'auth.opac_timeout',
1235   'OPAC Inactivity Timeout (in seconds)',
1236   null,
1237   'integer' ),
1238
1239 ( 'auth.staff_timeout',
1240   'Staff Login Inactivity Timeout (in seconds)',
1241   null,
1242   'integer' ),
1243
1244 ( 'circ.lost_materials_processing_fee',
1245   'Lost Materials Processing Fee',
1246   null,
1247   'currency' ),
1248
1249 ( 'cat.default_item_price',
1250   'Default Item Price',
1251   null,
1252   'currency' ),
1253
1254 ( 'org.bounced_emails',
1255   'Sending email address for patron notices',
1256   null,
1257   'string' ),
1258
1259 ( 'circ.hold_expire_alert_interval',
1260   'Holds: Expire Alert Interval',
1261   'Amount of time before a hold expires at which point the patron should be alerted',
1262   'interval' ),
1263
1264 ( 'circ.hold_expire_interval',
1265   'Holds: Expire Interval',
1266   'Amount of time after a hold is placed before the hold expires.  Example "100 days"',
1267   'interval' ),
1268
1269 ( 'credit.payments.allow',
1270   'Allow Credit Card Payments',
1271   'If enabled, patrons will be able to pay fines accrued at this location via credit card',
1272   'bool' ),
1273
1274 ( 'global.default_locale',
1275   'Global Default Locale',
1276   null,
1277   'string' ),
1278
1279 ( 'circ.void_overdue_on_lost',
1280   'Void overdue fines when items are marked lost',
1281   null,
1282   'bool' ),
1283
1284 ( 'circ.hold_stalling.soft',
1285   'Holds: Soft stalling interval',
1286   'How long to wait before allowing remote items to be opportunistically captured for a hold.  Example "5 days"',
1287   'interval' ),
1288
1289 ( 'circ.hold_stalling_hard',
1290   'Holds: Hard stalling interval',
1291   '',
1292   'interval' ),
1293
1294 ( 'circ.hold_boundary.hard',
1295   'Holds: Hard boundary',
1296   null,
1297   'integer' ),
1298
1299 ( 'circ.hold_boundary.soft',
1300   'Holds: Soft boundary',
1301   null,
1302   'integer' ),
1303
1304 ( 'opac.barcode_regex',
1305   'Patron barcode format',
1306   'Regular expression defining the patron barcode format',
1307   'string' ),
1308
1309 ( 'global.password_regex',
1310   'Password format',
1311   'Regular expression defining the password format',
1312   'string' ),
1313
1314 ( 'circ.item_checkout_history.max',
1315   'Maximum previous checkouts displayed',
1316   'This is the maximum number of previous circulations the staff client will display when investigating item details',
1317   'integer' ),
1318
1319 ( 'circ.reshelving_complete.interval',
1320   'Change reshelving status interval',
1321   'Amount of time to wait before changing an item from "reshelving" status to "available".  Examples: "1 day", "6 hours"',
1322   'interval' ),
1323
1324 ( 'circ.holds.default_estimated_wait_interval',
1325   'Holds: Default Estimated Wait',
1326   '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.',
1327   'interval' ),
1328
1329 ( 'circ.holds.min_estimated_wait_interval',
1330   'Holds: Minimum Estimated Wait',
1331   '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.',
1332   'interval' ),
1333
1334 ( 'circ.selfcheck.patron_login_timeout',
1335   'Selfcheck: Patron Login Timeout (in seconds)',
1336   'Number of seconds of inactivity before the patron is logged out of the selfcheck interface',
1337   'integer' ),
1338
1339 ( 'circ.selfcheck.alert.popup',
1340   'Selfcheck: Pop-up alert for errors',
1341   'If true, checkout/renewal errors will cause a pop-up window in addition to the on-screen message',
1342   'bool' ),
1343
1344 ( 'circ.selfcheck.require_patron_password',
1345   'Selfcheck: Require patron password',
1346   'If true, patrons will be required to enter their password in addition to their username/barcode to log into the selfcheck interface',
1347   'bool' ),
1348
1349 ( 'global.juvenile_age_threshold',
1350   'Juvenile Age Threshold',
1351   'The age at which a user is no long considered a juvenile.  For example, "18 years".',
1352   'interval' ),
1353
1354 ( 'cat.bib.keep_on_empty',
1355   'Retain empty bib records',
1356   'Retain a bib record even when all attached copies are deleted',
1357   'bool' ),
1358
1359 ( 'cat.bib.alert_on_empty',
1360   'Alert on empty bib records',
1361   'Alert staff when the last copy for a record is being deleted',
1362   'bool' ),
1363
1364 ( 'patron.password.use_phone',
1365   'Patron: password from phone #',
1366   'Use the last 4 digits of the patrons phone number as the default password when creating new users',
1367   'bool' ),
1368
1369 ( 'circ.charge_on_damaged',
1370   'Charge item price when marked damaged',
1371   'Charge item price when marked damaged',
1372   'bool' ),
1373
1374 ( 'circ.charge_lost_on_zero',
1375   'Charge lost on zero',
1376   '',
1377   'bool' ),
1378
1379 ( 'circ.damaged_item_processing_fee',
1380   'Charge processing fee for damaged items',
1381   'Charge processing fee for damaged items',
1382   'currency' ),
1383
1384 ( 'circ.void_lost_on_checkin',
1385   'Circ: Void lost item billing when returned',
1386   'Void lost item billing when returned',
1387   'bool' ),
1388
1389 ( 'circ.max_accept_return_of_lost',
1390   'Circ: Void lost max interval',
1391   'Items that have been lost this long will not result in voided billings when returned.  E.g. ''6 months''',
1392   'interval' ),
1393
1394 ( 'circ.void_lost_proc_fee_on_checkin',
1395   'Circ: Void processing fee on lost item return',
1396   'Void processing fee when lost item returned',
1397   'bool' ),
1398
1399 ( 'circ.restore_overdue_on_lost_return',
1400   'Circ: Restore overdues on lost item return',
1401   'Restore overdue fines on lost item return',
1402   'bool' ),
1403
1404 ( 'circ.lost_immediately_available',
1405   'Circ: Lost items usable on checkin',
1406   'Lost items are usable on checkin instead of going ''home'' first',
1407   'bool' ),
1408
1409 ( 'circ.holds_fifo',
1410   'Holds: FIFO',
1411   'Force holds to a more strict First-In, First-Out capture',
1412   'bool' ),
1413
1414 ( 'opac.allow_pending_address',
1415   'OPAC: Allow pending addresses',
1416   'If enabled, patrons can create and edit existing addresses.  Addresses are kept in a pending state until staff approves the changes',
1417   'bool' ),
1418
1419 ( 'ui.circ.show_billing_tab_on_bills',
1420   'Show billing tab first when bills are present',
1421   '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',
1422   'bool' ),
1423
1424 ( 'ui.general.idle_timeout',
1425     'GUI: Idle timeout',
1426     '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).',
1427     'integer' ),
1428
1429 ( 'ui.circ.in_house_use.entry_cap',
1430   'GUI: Record In-House Use: Maximum # of uses allowed per entry.',
1431   'The # of uses entry in the Record In-House Use interface may not exceed the value of this setting.',
1432   'integer' ),
1433
1434 ( 'ui.circ.in_house_use.entry_warn',
1435   'GUI: Record In-House Use: # of uses threshold for Are You Sure? dialog.',
1436   'In the Record In-House Use interface, a submission attempt will warn if the # of uses field exceeds the value of this setting.',
1437   'integer' ),
1438
1439 ( 'acq.default_circ_modifier',
1440   'Default circulation modifier',
1441   null,
1442   'string' ),
1443
1444 ( 'acq.tmp_barcode_prefix',
1445   'Temporary barcode prefix',
1446   null,
1447   'string' ),
1448
1449 ( 'acq.tmp_callnumber_prefix',
1450   'Temporary call number prefix',
1451   null,
1452   'string' ),
1453
1454 ( 'ui.circ.patron_summary.horizontal',
1455   'Patron circulation summary is horizontal',
1456   null,
1457   'bool' ),
1458
1459 ( 'ui.staff.require_initials',
1460   oils_i18n_gettext('ui.staff.require_initials', 'GUI: Require staff initials for entry/edit of item/patron/penalty notes/messages.', 'coust', 'label'),
1461   oils_i18n_gettext('ui.staff.require_initials', 'Appends staff initials and edit date into note content.', 'coust', 'description'),
1462   'bool' ),
1463
1464 ( 'ui.general.button_bar',
1465   'Button bar',
1466   null,
1467   'bool' ),
1468
1469 ( 'circ.hold_shelf_status_delay',
1470   'Hold Shelf Status Delay',
1471   '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.',
1472   'interval' ),
1473
1474 ( 'circ.patron_invalid_address_apply_penalty',
1475   'Invalid patron address penalty',
1476   'When set, if a patron address is set to invalid, a penalty is applied.',
1477   'bool' ),
1478
1479 ( 'circ.checkout_fills_related_hold',
1480   'Checkout Fills Related Hold',
1481   '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',
1482   'bool'),
1483
1484 ( 'circ.selfcheck.auto_override_checkout_events',
1485   'Selfcheck override events list',
1486   'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction',
1487   'array' ),
1488
1489 ( 'circ.staff_client.do_not_auto_attempt_print',
1490   'Disable Automatic Print Attempt Type List',
1491   '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).',
1492   'array' ),
1493
1494 ( 'ui.patron.default_inet_access_level',
1495   'Default level of patrons'' internet access',
1496   null,
1497   'integer' ),
1498
1499 ( 'circ.max_patron_claim_return_count',
1500     'Max Patron Claims Returned Count',
1501     'When this count is exceeded, a staff override is required to mark the item as claims returned',
1502     'integer' ),
1503
1504 ( 'circ.obscure_dob',
1505     'Obscure the Date of Birth field',
1506     '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.',
1507     'bool' ),
1508
1509 ( 'circ.auto_hide_patron_summary',
1510     'GUI: Toggle off the patron summary sidebar after first view.',
1511     'When true, the patron summary sidebar will collapse after a new patron sub-interface is selected.',
1512     'bool' ),
1513
1514 ( 'credit.processor.default',
1515     'Credit card processing: Name default credit processor',
1516     'This can be "AuthorizeNet", "PayPal" (for the Website Payment Pro API), or "PayflowPro".',
1517     'string' ),
1518
1519 ( 'credit.processor.authorizenet.enabled',
1520     'Credit card processing: AuthorizeNet enabled',
1521     '',
1522     'bool' ),
1523
1524 ( 'credit.processor.authorizenet.login',
1525     'Credit card processing: AuthorizeNet login',
1526     '',
1527     'string' ),
1528
1529 ( 'credit.processor.authorizenet.password',
1530     'Credit card processing: AuthorizeNet password',
1531     '',
1532     'string' ),
1533
1534 ( 'credit.processor.authorizenet.server',
1535     'Credit card processing: AuthorizeNet server',
1536     'Required if using a developer/test account with AuthorizeNet',
1537     'string' ),
1538
1539 ( 'credit.processor.authorizenet.testmode',
1540     'Credit card processing: AuthorizeNet test mode',
1541     '',
1542     'bool' ),
1543
1544 ( 'credit.processor.paypal.enabled',
1545     'Credit card processing: PayPal enabled',
1546     '',
1547     'bool' ),
1548 ( 'credit.processor.paypal.login',
1549     'Credit card processing: PayPal login',
1550     '',
1551     'string' ),
1552 ( 'credit.processor.paypal.password',
1553     'Credit card processing: PayPal password',
1554     '',
1555     'string' ),
1556 ( 'credit.processor.paypal.signature',
1557     'Credit card processing: PayPal signature',
1558     '',
1559     'string' ),
1560 ( 'credit.processor.paypal.testmode',
1561     'Credit card processing: PayPal test mode',
1562     '',
1563     'bool' ),
1564
1565 ( 'ui.admin.work_log.max_entries',
1566     oils_i18n_gettext('ui.admin.work_log.max_entries', 'GUI: Work Log: Maximum Actions Logged', 'coust', 'label'),
1567     oils_i18n_gettext('ui.admin.work_log.max_entries', 'Maximum entries for "Most Recent Staff Actions" section of the Work Log interface.', 'coust', 'description'),
1568   'interval' ),
1569
1570 ( 'ui.admin.patron_log.max_entries',
1571     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'GUI: Work Log: Maximum Patrons Logged', 'coust', 'label'),
1572     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'Maximum entries for "Most Recently Affected Patrons..." section of the Work Log interface.', 'coust', 'description'),
1573   'interval' ),
1574
1575 ( 'lib.courier_code',
1576     oils_i18n_gettext('lib.courier_code', 'Courier Code', 'coust', 'label'),
1577     oils_i18n_gettext('lib.courier_code', 'Courier Code for the library.  Available in transit slip templates as the %courier_code% macro.', 'coust', 'description'),
1578     'string'),
1579
1580 ( 'circ.block_renews_for_holds',
1581     oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
1582     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'),
1583     'bool' ),
1584
1585 ( 'circ.password_reset_request_per_user_limit',
1586     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
1587     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'),
1588     'string'),
1589
1590 ( 'circ.password_reset_request_time_to_live',
1591     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
1592     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'),
1593     'string'),
1594
1595 ( 'circ.password_reset_request_throttle',
1596     oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
1597     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'),
1598     'string')
1599 ;
1600
1601 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1602         'ui.circ.suppress_checkin_popups',
1603         oils_i18n_gettext(
1604             'ui.circ.suppress_checkin_popups', 
1605             'Circ: Suppress popup-dialogs during check-in.', 
1606             'coust', 
1607             'label'),
1608         oils_i18n_gettext(
1609             'ui.circ.suppress_checkin_popups', 
1610             'Circ: Suppress popup-dialogs during check-in.', 
1611             'coust', 
1612             'description'),
1613         'bool'
1614 );
1615
1616 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1617         'format.date',
1618         oils_i18n_gettext(
1619             'format.date',
1620             'GUI: Format Dates with this pattern.', 
1621             'coust', 
1622             'label'),
1623         oils_i18n_gettext(
1624             'format.date',
1625             'GUI: Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010")', 
1626             'coust', 
1627             'description'),
1628         'string'
1629 ), (
1630         'format.time',
1631         oils_i18n_gettext(
1632             'format.time',
1633             'GUI: Format Times with this pattern.', 
1634             'coust', 
1635             'label'),
1636         oils_i18n_gettext(
1637             'format.time',
1638             '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")', 
1639             'coust', 
1640             'description'),
1641         'string'
1642 );
1643
1644 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1645         'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1646         oils_i18n_gettext(
1647             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1648             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1649             'coust', 
1650             'label'),
1651         oils_i18n_gettext(
1652             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1653             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1654             'coust', 
1655             'description'),
1656         'bool'
1657 );
1658
1659 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1660         'url.remote_column_settings',
1661         oils_i18n_gettext(
1662             'url.remote_column_settings',
1663             'GUI: URL for remote directory containing list column settings.', 
1664             'coust', 
1665             'label'),
1666         oils_i18n_gettext(
1667             'url.remote_column_settings',
1668             '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.', 
1669             'coust', 
1670             'description'),
1671         'string'
1672 );
1673
1674 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1675         'gui.disable_local_save_columns',
1676         oils_i18n_gettext(
1677             'gui.disable_local_save_columns',
1678             'GUI: Disable the ability to save list column configurations locally.', 
1679             'coust', 
1680             'label'),
1681         oils_i18n_gettext(
1682             'gui.disable_local_save_columns',
1683             '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.', 
1684             'coust', 
1685             'description'),
1686         'bool'
1687 );
1688
1689 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1690         'circ.password_reset_request_requires_matching_email',
1691         oils_i18n_gettext(
1692             'circ.password_reset_request_requires_matching_email',
1693             'Circulation: Require matching email address for password reset requests', 
1694             'coust', 
1695             'label'),
1696         oils_i18n_gettext(
1697             'circ.password_reset_request_requires_matching_email',
1698             'Circulation: Require matching email address for password reset requests', 
1699             'coust', 
1700             'description'),
1701         'bool'
1702 );
1703
1704 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1705         'circ.holds.expired_patron_block',
1706         oils_i18n_gettext(
1707             'circ.holds.expired_patron_block',
1708             'Circulation: Block hold request if hold recipient privileges have expired', 
1709             'coust', 
1710             'label'),
1711         oils_i18n_gettext(
1712             'circ.holds.expired_patron_block',
1713             'Circulation: Block hold request if hold recipient privileges have expired', 
1714             'coust', 
1715             'description'),
1716         'bool'
1717 );
1718
1719 INSERT INTO config.org_unit_setting_type
1720     (name, label, description, datatype) VALUES (
1721         'circ.booking_reservation.default_elbow_room',
1722         oils_i18n_gettext(
1723             'circ.booking_reservation.default_elbow_room',
1724             'Booking: Elbow room',
1725             'coust',
1726             'label'
1727         ),
1728         oils_i18n_gettext(
1729             'circ.booking_reservation.default_elbow_room',
1730             '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.',
1731             'coust',
1732             'label'
1733         ),
1734         'interval'
1735     );
1736
1737 -- Org_unit_setting_type(s) that need an fm_class:
1738 INSERT into config.org_unit_setting_type
1739 ( name, label, description, datatype, fm_class ) VALUES
1740 ( 'acq.default_copy_location',
1741   'Default copy location',
1742   null,
1743   'link',
1744   'acpl' );
1745
1746 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1747     'circ.holds.org_unit_target_weight',
1748     'Holds: Org Unit Target Weight',
1749     '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.',
1750     'integer'
1751 );
1752
1753 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1754     'circ.holds.target_holds_by_org_unit_weight',
1755     'Holds: Use weight-based hold targeting',
1756     'Use library weight based hold targeting',
1757     'bool'
1758 );
1759
1760 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1761     'circ.holds.max_org_unit_target_loops',
1762     'Holds: Maximum library target attempts',
1763     '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',
1764     'integer'
1765 );
1766
1767
1768 -- Org setting for overriding the circ lib of a precat copy
1769 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1770     'circ.pre_cat_copy_circ_lib',
1771     'Pre-cat Item Circ Lib',
1772     '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',
1773     'string'
1774 );
1775
1776 -- Circ auto-renew interval setting
1777 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1778     'circ.checkout_auto_renew_age',
1779     'Checkout auto renew age',
1780     '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',
1781     'interval'
1782 );
1783
1784 -- Setting for behind the desk hold pickups
1785 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1786     'circ.holds.behind_desk_pickup_supported',
1787     'Holds: Behind Desk Pickup Supported',
1788     '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',
1789     'bool'
1790 );
1791
1792 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1793         'acq.holds.allow_holds_from_purchase_request',
1794         oils_i18n_gettext(
1795             'acq.holds.allow_holds_from_purchase_request', 
1796             'Allows patrons to create automatic holds from purchase requests.', 
1797             'coust', 
1798             'label'),
1799         oils_i18n_gettext(
1800             'acq.holds.allow_holds_from_purchase_request', 
1801             'Allows patrons to create automatic holds from purchase requests.', 
1802             'coust', 
1803             'description'),
1804         'bool'
1805 );
1806
1807 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1808     'circ.holds.target_skip_me',
1809     'Skip For Hold Targeting',
1810     'When true, don''t target any copies at this org unit for holds',
1811     'bool'
1812 );
1813
1814 -- claims returned mark item missing 
1815 INSERT INTO
1816     config.org_unit_setting_type ( name, label, description, datatype )
1817     VALUES (
1818         'circ.claim_return.mark_missing',
1819         'Claim Return: Mark copy as missing', 
1820         'When a circ is marked as claims-returned, also mark the copy as missing',
1821         'bool'
1822     );
1823
1824 -- claims never checked out mark item missing 
1825 INSERT INTO
1826     config.org_unit_setting_type ( name, label, description, datatype )
1827     VALUES (
1828         'circ.claim_never_checked_out.mark_missing',
1829         'Claim Never Checked Out: Mark copy as missing', 
1830         'When a circ is marked as claims-never-checked-out, mark the copy as missing',
1831         'bool'
1832     );
1833
1834 -- mark damaged void overdue setting
1835 INSERT INTO
1836     config.org_unit_setting_type ( name, label, description, datatype )
1837     VALUES (
1838         'circ.damaged.void_ovedue',
1839         'Mark item damaged voids overdues',
1840         'When an item is marked damaged, overdue fines on the most recent circulation are voided.',
1841         'bool'
1842     );
1843
1844 -- hold cancel display limits
1845 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1846     VALUES (
1847         'circ.holds.canceled.display_count',
1848         'Holds: Canceled holds display count',
1849         'How many canceled holds to show in patron holds interfaces',
1850         'integer'
1851     );
1852
1853 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1854     VALUES (
1855         'circ.holds.canceled.display_age',
1856         'Holds: Canceled holds display age',
1857         'Show all canceled holds that were canceled within this amount of time',
1858         'interval'
1859     );
1860
1861 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1862     VALUES (
1863         'circ.holds.uncancel.reset_request_time',
1864         'Holds: Reset request time on un-cancel',
1865         'When a hold is uncanceled, reset the request time to push it to the end of the queue',
1866         'bool'
1867     );
1868
1869 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1870     VALUES (
1871         'circ.holds.default_shelf_expire_interval',
1872         'Default hold shelf expire interval',
1873         '',
1874         'interval'
1875 );
1876
1877 INSERT INTO config.org_unit_setting_type (name, label, description, datatype, fm_class)
1878     VALUES (
1879         'circ.claim_return.copy_status', 
1880         'Claim Return Copy Status', 
1881         'Claims returned copies are put into this status.  Default is to leave the copy in the Checked Out status',
1882         'link', 
1883         'ccs' 
1884     );
1885
1886 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
1887     VALUES ( 
1888         'circ.max_fine.cap_at_price',
1889         oils_i18n_gettext('circ.max_fine.cap_at_price', 'Circ: Cap Max Fine at Item Price', 'coust', 'label'),
1890         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'),
1891         'bool' 
1892     );
1893
1894 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) 
1895     VALUES ( 
1896         'circ.holds.clear_shelf.copy_status',
1897         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Holds: Clear shelf copy status', 'coust', 'label'),
1898         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'),
1899         'link',
1900         'ccs'
1901     );
1902
1903 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1904     VALUES ( 
1905         'circ.selfcheck.workstation_required',
1906         oils_i18n_gettext('circ.selfcheck.workstation_required', 'Selfcheck: Workstation Required', 'coust', 'label'),
1907         oils_i18n_gettext('circ.selfcheck.workstation_required', 'All selfcheck stations must use a workstation', 'coust', 'description'),
1908         'bool'
1909     ), (
1910         'circ.selfcheck.patron_password_required',
1911         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Selfcheck: Require Patron Password', 'coust', 'label'),
1912         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Patron must log in with barcode and password at selfcheck station', 'coust', 'description'),
1913         'bool'
1914     );
1915
1916 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1917     VALUES ( 
1918         'circ.selfcheck.alert.sound',
1919         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Selfcheck: Audio Alerts', 'coust', 'label'),
1920         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Use audio alerts for selfcheck events', 'coust', 'description'),
1921         'bool'
1922     );
1923
1924 INSERT INTO
1925     config.org_unit_setting_type (name, label, description, datatype)
1926     VALUES (
1927         'notice.telephony.callfile_lines',
1928         'Telephony: Arbitrary line(s) to include in each notice callfile',
1929         $$
1930         This overrides lines from opensrf.xml.
1931         Line(s) must be valid for your target server and platform
1932         (e.g. Asterisk 1.4).
1933         $$,
1934         'string'
1935     );
1936
1937 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1938     VALUES ( 
1939         'circ.offline.username_allowed',
1940         oils_i18n_gettext('circ.offline.username_allowed', 'Offline: Patron Usernames Allowed', 'coust', 'label'),
1941         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'),
1942         'bool'
1943     );
1944
1945 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1946 VALUES (
1947     'acq.fund.balance_limit.warn',
1948     oils_i18n_gettext('acq.fund.balance_limit.warn', 'Fund Spending Limit for Warning', 'coust', 'label'),
1949     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'),
1950     'integer'
1951 );
1952
1953 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1954 VALUES (
1955     'acq.fund.balance_limit.block',
1956     oils_i18n_gettext('acq.fund.balance_limit.block', 'Fund Spending Limit for Block', 'coust', 'label'),
1957     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'),
1958     'integer'
1959 );
1960
1961 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1962     VALUES (
1963         'circ.holds.hold_has_copy_at.alert',
1964         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'Holds: Has Local Copy Alert', 'coust', 'label'),
1965         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'),
1966         'bool'
1967     ),(
1968         'circ.holds.hold_has_copy_at.block',
1969         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'Holds: Has Local Copy Block', 'coust', 'label'),
1970         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'),
1971         'bool'
1972     );
1973
1974 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1975 VALUES (
1976     'auth.persistent_login_interval',
1977     oils_i18n_gettext('auth.persistent_login_interval', 'Persistent Login Duration', 'coust', 'label'),
1978     oils_i18n_gettext('auth.persistent_login_interval', 'How long a persistent login lasts.  E.g. ''2 weeks''', 'coust', 'description'),
1979     'interval'
1980 );
1981
1982 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1983         'cat.marc_control_number_identifier',
1984         oils_i18n_gettext(
1985             'cat.marc_control_number_identifier', 
1986             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
1987             'coust', 
1988             'label'),
1989         oils_i18n_gettext(
1990             'cat.marc_control_number_identifier', 
1991             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
1992             'coust', 
1993             'description'),
1994         'string'
1995 );
1996
1997 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) 
1998     VALUES (
1999         'circ.selfcheck.block_checkout_on_copy_status',
2000         oils_i18n_gettext(
2001             'circ.selfcheck.block_checkout_on_copy_status',
2002             'Selfcheck: Block copy checkout status',
2003             'coust',
2004             'label'
2005         ),
2006         oils_i18n_gettext(
2007             'circ.selfcheck.block_checkout_on_copy_status',
2008             'List of copy status IDs that will block checkout even if the generic COPY_NOT_AVAILABLE event is overridden',
2009             'coust',
2010             'description'
2011         ),
2012         'array'
2013     );
2014
2015 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2016 VALUES (
2017     'serial.prev_issuance_copy_location',
2018     oils_i18n_gettext(
2019         'serial.prev_issuance_copy_location',
2020         'Serials: Previous Issuance Copy Location',
2021         'coust',
2022         'label'
2023     ),
2024     oils_i18n_gettext(
2025         'serial.prev_issuance_copy_location',
2026         'When a serial issuance is received, copies (units) of the previous issuance will be automatically moved into the configured shelving location',
2027         'coust',
2028         'descripton'
2029         ),
2030     'link',
2031     'acpl'
2032 );
2033
2034 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2035 VALUES (
2036     'cat.default_classification_scheme',
2037     oils_i18n_gettext(
2038         'cat.default_classification_scheme',
2039         'Cataloging: Default Classification Scheme',
2040         'coust',
2041         'label'
2042     ),
2043     oils_i18n_gettext(
2044         'cat.default_classification_scheme',
2045         'Defines the default classification scheme for new call numbers: 1 = Generic; 2 = Dewey; 3 = LC',
2046         'coust',
2047         'descripton'
2048         ),
2049     'link',
2050     'acnc'
2051 );
2052
2053 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2054         'opac.org_unit_hiding.depth',
2055         oils_i18n_gettext(
2056             'opac.org_unit_hiding.depth',
2057             'OPAC: Org Unit Hiding Depth', 
2058             'coust', 
2059             'label'),
2060         oils_i18n_gettext(
2061             'opac.org_unit_hiding.depth',
2062             '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.', 
2063             'coust', 
2064             'description'),
2065         'integer'
2066 );
2067
2068 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2069     VALUES 
2070         ('circ.holds.alert_if_local_avail',
2071          'Holds: Local available alert',
2072          'If local copy is available, alert the person making the hold',
2073          'bool'),
2074
2075         ('circ.holds.deny_if_local_avail',
2076          'Holds: Local available block',
2077          'If local copy is available, deny the creation of the hold',
2078          'bool')
2079     ;
2080
2081 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2082     VALUES ( 
2083         'circ.holds.clear_shelf.no_capture_holds',
2084         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'Holds: Bypass hold capture during clear shelf process', 'coust', 'label'),
2085         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'During the clear shelf process, avoid capturing new holds on cleared items.', 'coust', 'description'),
2086         'bool'
2087     );
2088
2089 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
2090     'circ.booking_reservation.stop_circ',
2091     'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
2092     '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.',
2093     'bool'
2094 );
2095
2096 ---------------------------------
2097 -- Seed data for usr_setting_type
2098 ----------------------------------
2099
2100 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2101     VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
2102
2103 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2104     VALUES ('opac.default_search_depth', TRUE, 'OPAC Search Depth', 'OPAC Search Depth', 'integer');
2105
2106 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2107     VALUES ('opac.default_search_location', TRUE, 'OPAC Search Location', 'OPAC Search Location', 'integer');
2108
2109 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2110     VALUES ('opac.hits_per_page', TRUE, 'Hits per Page', 'Hits per Page', 'string');
2111
2112 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2113     VALUES ('opac.hold_notify', TRUE, 'Hold Notification Format', 'Hold Notification Format', 'string');
2114
2115 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2116     VALUES ('staff_client.catalog.record_view.default', TRUE, 'Default Record View', 'Default Record View', 'string');
2117
2118 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2119     VALUES ('staff_client.copy_editor.templates', TRUE, 'Copy Editor Template', 'Copy Editor Template', 'object');
2120
2121 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2122     VALUES ('circ.holds_behind_desk', FALSE, 'Hold is behind Circ Desk', 'Hold is behind Circ Desk', 'bool');
2123
2124 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2125     VALUES (
2126         'history.circ.retention_age',
2127         TRUE,
2128         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','label'),
2129         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','description'),
2130         'interval'
2131     ),(
2132         'history.circ.retention_start',
2133         FALSE,
2134         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','label'),
2135         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','description'),
2136         'date'
2137     );
2138
2139 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2140     VALUES (
2141         'history.hold.retention_age',
2142         TRUE,
2143         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','label'),
2144         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','description'),
2145         'interval'
2146     ),(
2147         'history.hold.retention_start',
2148         TRUE,
2149         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','label'),
2150         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','description'),
2151         'interval'
2152     ),(
2153         'history.hold.retention_count',
2154         TRUE,
2155         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','label'),
2156         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','description'),
2157         'integer'
2158     );
2159
2160 INSERT INTO config.usr_setting_type (name, opac_visible, label, description, datatype)
2161     VALUES (
2162         'opac.default_sort',
2163         TRUE,
2164         oils_i18n_gettext(
2165             'opac.default_sort',
2166             'OPAC Default Search Sort',
2167             'cust',
2168             'label'
2169         ),
2170         oils_i18n_gettext(
2171             'opac.default_sort',
2172             'OPAC Default Search Sort',
2173             'cust',
2174             'description'
2175         ),
2176         'string'
2177     );
2178
2179 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2180         'circ.missing_pieces.copy_status',
2181         oils_i18n_gettext(
2182             'circ.missing_pieces.copy_status',
2183             'Circulation: Item Status for Missing Pieces',
2184             'coust',
2185             'label'),
2186         oils_i18n_gettext(
2187             'circ.missing_pieces.copy_status',
2188             '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.',
2189             'coust',
2190             'description'),
2191         'link',
2192         'ccs'
2193 );
2194
2195 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2196         'circ.do_not_tally_claims_returned',
2197         oils_i18n_gettext(
2198             'circ.do_not_tally_claims_returned',
2199             'Circulation: Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.',
2200             'coust',
2201             'label'),
2202         oils_i18n_gettext(
2203             'circ.do_not_tally_claims_returned',
2204             '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.',
2205             'coust',
2206             'description'),
2207         'bool'
2208 );
2209
2210 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2211     VALUES
2212         ('cat.label.font.size',
2213             oils_i18n_gettext('cat.label.font.size',
2214                 'Cataloging: Spine and pocket label font size', 'coust', 'label'),
2215             oils_i18n_gettext('cat.label.font.size',
2216                 'Set the default font size for spine and pocket labels', 'coust', 'description'),
2217             'integer'
2218         )
2219         ,('cat.label.font.family',
2220             oils_i18n_gettext('cat.label.font.family',
2221                 'Cataloging: Spine and pocket label font family', 'coust', 'label'),
2222             oils_i18n_gettext('cat.label.font.family',
2223                 '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".',
2224                 'coust', 'description'),
2225             'string'
2226         )
2227         ,('cat.spine.line.width',
2228             oils_i18n_gettext('cat.spine.line.width',
2229                 'Cataloging: Spine label line width', 'coust', 'label'),
2230             oils_i18n_gettext('cat.spine.line.width',
2231                 'Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.',
2232                 'coust', 'description'),
2233             'integer'
2234         )
2235         ,('cat.spine.line.height',
2236             oils_i18n_gettext('cat.spine.line.height',
2237                 'Cataloging: Spine label maximum lines', 'coust', 'label'),
2238             oils_i18n_gettext('cat.spine.line.height',
2239                 'Set the default maximum number of lines for spine labels.',
2240                 'coust', 'description'),
2241             'integer'
2242         )
2243         ,('cat.spine.line.margin',
2244             oils_i18n_gettext('cat.spine.line.margin',
2245                 'Cataloging: Spine label left margin', 'coust', 'label'),
2246             oils_i18n_gettext('cat.spine.line.margin',
2247                 'Set the left margin for spine labels in number of characters.',
2248                 'coust', 'description'),
2249             'integer'
2250         )
2251 ;
2252
2253 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2254     VALUES
2255         ('cat.label.font.weight',
2256             oils_i18n_gettext('cat.label.font.weight',
2257                 'Cataloging: Spine and pocket label font weight', 'coust', 'label'),
2258             oils_i18n_gettext('cat.label.font.weight',
2259                 'Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".',
2260                 'coust', 'description'),
2261             'string'
2262         )
2263 ;
2264
2265 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2266         'circ.patron_edit.clone.copy_address',
2267         oils_i18n_gettext(
2268             'circ.patron_edit.clone.copy_address',
2269             'Patron Registration: Cloned patrons get address copy',
2270             'coust',
2271             'label'
2272         ),
2273         oils_i18n_gettext(
2274             'circ.patron_edit.clone.copy_address',
2275             'In the Patron editor, copy addresses from the cloned user instead of linking directly to the address',
2276             'coust',
2277             'description'
2278         ),
2279         'bool'
2280 );
2281
2282 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2283         'ui.patron.default_ident_type',
2284         oils_i18n_gettext(
2285             'ui.patron.default_ident_type',
2286             'GUI: Default Ident Type for Patron Registration',
2287             'coust',
2288             'label'),
2289         oils_i18n_gettext(
2290             'ui.patron.default_ident_type',
2291             'This is the default Ident Type for new users in the patron editor.',
2292             'coust',
2293             'description'),
2294         'link',
2295         'cit'
2296 );
2297
2298 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2299         'ui.patron.default_country',
2300         oils_i18n_gettext(
2301             'ui.patron.default_country',
2302             'GUI: Default Country for New Addresses in Patron Editor',
2303             'coust',
2304             'label'),
2305         oils_i18n_gettext(
2306             'ui.patron.default_country',
2307             'This is the default Country for new addresses in the patron editor.',
2308             'coust',
2309             'description'),
2310         'string'
2311 );
2312
2313 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2314         'ui.patron.registration.require_address',
2315         oils_i18n_gettext(
2316             'ui.patron.registration.require_address',
2317             'GUI: Require at least one address for Patron Registration',
2318             'coust',
2319             'label'),
2320         oils_i18n_gettext(
2321             'ui.patron.registration.require_address',
2322             'Enforces a requirement for having at least one address for a patron during registration.',
2323             'coust',
2324             'description'),
2325         'bool'
2326 );
2327
2328 INSERT INTO config.org_unit_setting_type (
2329     name, label, description, datatype
2330 ) VALUES
2331     ('credit.processor.payflowpro.enabled',
2332         'Credit card processing: Enable PayflowPro payments',
2333         'This is NOT the same thing as the settings labeled with just "PayPal."',
2334         'bool'
2335     ),
2336     ('credit.processor.payflowpro.login',
2337         'Credit card processing: PayflowPro login/merchant ID',
2338         'Often the same thing as the PayPal manager login',
2339         'string'
2340     ),
2341     ('credit.processor.payflowpro.password',
2342         'Credit card processing: PayflowPro password',
2343         'PayflowPro password',
2344         'string'
2345     ),
2346     ('credit.processor.payflowpro.testmode',
2347         'Credit card processing: PayflowPro test mode',
2348         'Do not really process transactions, but stay in test mode - uses pilot-payflowpro.paypal.com instead of the usual host',
2349         'bool'
2350     ),
2351     ('credit.processor.payflowpro.vendor',
2352         'Credit card processing: PayflowPro vendor',
2353         'Often the same thing as the login',
2354         'string'
2355     ),
2356     ('credit.processor.payflowpro.partner',
2357         'Credit card processing: PayflowPro partner',
2358         'Often "PayPal" or "VeriSign", sometimes others',
2359         'string'
2360     );
2361
2362 -- Patch the name of an old selfcheck setting
2363 UPDATE actor.org_unit_setting
2364     SET name = 'circ.selfcheck.alert.popup'
2365     WHERE name = 'circ.selfcheck.alert_on_checkout_event';
2366
2367 -- Rename certain existing org_unit settings, if present,
2368 -- and make sure their values are JSON
2369 UPDATE actor.org_unit_setting SET
2370     name = 'circ.holds.default_estimated_wait_interval',
2371     --
2372     -- The value column should be JSON.  The old value should be a number,
2373     -- but it may or may not be quoted.  The following CASE behaves
2374     -- differently depending on whether value is quoted.  It is simplistic,
2375     -- and will be defeated by leading or trailing white space, or various
2376     -- malformations.
2377     --
2378     value = CASE WHEN SUBSTR( value, 1, 1 ) = '"'
2379                 THEN '"' || SUBSTR( value, 2, LENGTH(value) - 2 ) || ' days"'
2380                 ELSE '"' || value || ' days"'
2381             END
2382 WHERE name = 'circ.hold_estimate_wait_interval';
2383
2384 -- Create types for existing org unit settings
2385 -- not otherwise accounted for
2386
2387 INSERT INTO config.org_unit_setting_type(
2388  name,
2389  label,
2390  description
2391 )
2392 SELECT DISTINCT
2393         name,
2394         name,
2395         'FIXME'
2396 FROM
2397         actor.org_unit_setting
2398 WHERE
2399         name NOT IN (
2400                 SELECT name
2401                 FROM config.org_unit_setting_type
2402         );
2403
2404 -- Add foreign key to org_unit_setting
2405
2406 ALTER TABLE actor.org_unit_setting
2407         ADD FOREIGN KEY (name) REFERENCES config.org_unit_setting_type (name)
2408                 DEFERRABLE INITIALLY DEFERRED;
2409
2410 -- Create types for existing user settings
2411 -- not otherwise accounted for
2412
2413 INSERT INTO config.usr_setting_type (
2414         name,
2415         label,
2416         description
2417 )
2418 SELECT DISTINCT
2419         name,
2420         name,
2421         'FIXME'
2422 FROM
2423         actor.usr_setting
2424 WHERE
2425         name NOT IN (
2426                 SELECT name
2427                 FROM config.usr_setting_type
2428         );
2429
2430 -- Add foreign key to user_setting_type
2431
2432 ALTER TABLE actor.usr_setting
2433         ADD FOREIGN KEY (name) REFERENCES config.usr_setting_type (name)
2434                 ON DELETE CASCADE ON UPDATE CASCADE
2435                 DEFERRABLE INITIALLY DEFERRED;
2436
2437 INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES
2438     (1, 'cat.spine.line.margin', 0)
2439     ,(1, 'cat.spine.line.height', 9)
2440     ,(1, 'cat.spine.line.width', 8)
2441     ,(1, 'cat.label.font.family', '"monospace"')
2442     ,(1, 'cat.label.font.size', 10)
2443     ,(1, 'cat.label.font.weight', '"normal"')
2444 ;
2445
2446 ALTER TABLE action_trigger.event_definition ADD COLUMN granularity TEXT;
2447 ALTER TABLE action_trigger.event ADD COLUMN async_output BIGINT REFERENCES action_trigger.event_output (id);
2448 ALTER TABLE action_trigger.event_definition ADD COLUMN usr_field TEXT;
2449 ALTER TABLE action_trigger.event_definition ADD COLUMN opt_in_setting TEXT REFERENCES config.usr_setting_type (name) DEFERRABLE INITIALLY DEFERRED;
2450
2451 CREATE OR REPLACE FUNCTION is_json( TEXT ) RETURNS BOOL AS $f$
2452     use JSON::XS;
2453     my $json = shift();
2454     eval { JSON::XS->new->allow_nonref->decode( $json ) };
2455     return $@ ? 0 : 1;
2456 $f$ LANGUAGE PLPERLU;
2457
2458 ALTER TABLE action_trigger.event ADD COLUMN user_data TEXT CHECK (user_data IS NULL OR is_json( user_data ));
2459
2460 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2461     'hold_request.cancel.expire_no_target',
2462     'ahr',
2463     'A hold is cancelled because no copies were found'
2464 );
2465
2466 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2467     'hold_request.cancel.expire_holds_shelf',
2468     'ahr',
2469     'A hold is cancelled because it was on the holds shelf too long'
2470 );
2471
2472 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2473     'hold_request.cancel.staff',
2474     'ahr',
2475     'A hold is cancelled because it was cancelled by staff'
2476 );
2477
2478 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2479     'hold_request.cancel.patron',
2480     'ahr',
2481     'A hold is cancelled by the patron'
2482 );
2483
2484 -- Fix typos in descriptions
2485 UPDATE action_trigger.hook SET description = 'A hold is successfully placed' WHERE key = 'hold_request.success';
2486 UPDATE action_trigger.hook SET description = 'A hold is attempted but not successfully placed' WHERE key = 'hold_request.failure';
2487
2488 -- Add a hook for renewals
2489 INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('renewal','circ','Item renewed to user');
2490
2491 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');
2492
2493 -- Sample Pre-due Notice --
2494
2495 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template) 
2496     VALUES (6, 'f', 1, '3 Day Courtesy Notice', 'checkout.due', 'CircIsOpen', 'SendEmail', '-3 days', 'due_date', 'usr', 
2497 $$
2498 [%- USE date -%]
2499 [%- user = target.0.usr -%]
2500 To: [%- params.recipient_email || user.email %]
2501 From: [%- params.sender_email || default_sender %]
2502 Subject: Courtesy Notice
2503
2504 Dear [% user.family_name %], [% user.first_given_name %]
2505 As a reminder, the following items are due in 3 days.
2506
2507 [% FOR circ IN target %]
2508     Title: [% circ.target_copy.call_number.record.simple_record.title %] 
2509     Barcode: [% circ.target_copy.barcode %] 
2510     Due: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2511     Item Cost: [% helpers.get_copy_price(circ.target_copy) %]
2512     Library: [% circ.circ_lib.name %]
2513     Library Phone: [% circ.circ_lib.phone %]
2514 [% END %]
2515
2516 $$);
2517
2518 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2519     (6, 'target_copy.call_number.record.simple_record'),
2520     (6, 'usr'),
2521     (6, 'circ_lib.billing_address');
2522
2523 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2524     (6, 'max_delay_age', '"1 day"');
2525
2526 -- also add the max delay age to the default overdue notice event def
2527 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2528     (1, 'max_delay_age', '"1 day"');
2529   
2530 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');
2531
2532 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.');
2533
2534 INSERT INTO action_trigger.hook (
2535         key,
2536         core_type,
2537         description,
2538         passive
2539     ) VALUES (
2540         'hold_request.shelf_expires_soon',
2541         'ahr',
2542         'A hold on the shelf will expire there soon.',
2543         TRUE
2544     );
2545
2546 INSERT INTO action_trigger.event_definition (
2547         id,
2548         active,
2549         owner,
2550         name,
2551         hook,
2552         validator,
2553         reactor,
2554         delay,
2555         delay_field,
2556         group_field,
2557         template
2558     ) VALUES (
2559         7,
2560         FALSE,
2561         1,
2562         'Hold Expires from Shelf Soon',
2563         'hold_request.shelf_expires_soon',
2564         'HoldIsAvailable',
2565         'SendEmail',
2566         '- 1 DAY',
2567         'shelf_expire_time',
2568         'usr',
2569 $$
2570 [%- USE date -%]
2571 [%- user = target.0.usr -%]
2572 To: [%- params.recipient_email || user.email %]
2573 From: [%- params.sender_email || default_sender %]
2574 Subject: Hold Available Notification
2575
2576 Dear [% user.family_name %], [% user.first_given_name %]
2577 You requested holds on the following item(s), which are available for
2578 pickup, but these holds will soon expire.
2579
2580 [% FOR hold IN target %]
2581     [%- data = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
2582     Title: [% data.title %]
2583     Author: [% data.author %]
2584     Library: [% hold.pickup_lib.name %]
2585 [% END %]
2586 $$
2587     );
2588
2589 INSERT INTO action_trigger.environment (
2590         event_def,
2591         path
2592     ) VALUES
2593     ( 7, 'current_copy'),
2594     ( 7, 'pickup_lib.billing_address'),
2595     ( 7, 'usr');
2596
2597 INSERT INTO action_trigger.hook (
2598         key,
2599         core_type,
2600         description,
2601         passive
2602     ) VALUES (
2603         'hold_request.long_wait',
2604         'ahr',
2605         'A patron has been waiting on a hold to be fulfilled for a long time.',
2606         TRUE
2607     );
2608
2609 INSERT INTO action_trigger.event_definition (
2610         id,
2611         active,
2612         owner,
2613         name,
2614         hook,
2615         validator,
2616         reactor,
2617         delay,
2618         delay_field,
2619         group_field,
2620         template
2621     ) VALUES (
2622         9,
2623         FALSE,
2624         1,
2625         'Hold waiting for pickup for long time',
2626         'hold_request.long_wait',
2627         'NOOP_True',
2628         'SendEmail',
2629         '6 MONTHS',
2630         'request_time',
2631         'usr',
2632 $$
2633 [%- USE date -%]
2634 [%- user = target.0.usr -%]
2635 To: [%- params.recipient_email || user.email %]
2636 From: [%- params.sender_email || default_sender %]
2637 Subject: Long Wait Hold Notification
2638
2639 Dear [% user.family_name %], [% user.first_given_name %]
2640
2641 You requested hold(s) on the following item(s), but unfortunately
2642 we have not been able to fulfill your request after a considerable
2643 length of time.  If you would still like to receive these items,
2644 no action is required.
2645
2646 [% FOR hold IN target %]
2647     Title: [% hold.bib_rec.bib_record.simple_record.title %]
2648     Author: [% hold.bib_rec.bib_record.simple_record.author %]
2649 [% END %]
2650 $$
2651 );
2652
2653 INSERT INTO action_trigger.environment (
2654         event_def,
2655         path
2656     ) VALUES
2657     (9, 'pickup_lib'),
2658     (9, 'usr'),
2659     (9, 'bib_rec.bib_record.simple_record');
2660
2661 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2662     VALUES (
2663         'format.selfcheck.checkout',
2664         'circ',
2665         'Formats circ objects for self-checkout receipt',
2666         TRUE
2667     );
2668
2669 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2670     VALUES (
2671         10,
2672         TRUE,
2673         1,
2674         'Self-Checkout Receipt',
2675         'format.selfcheck.checkout',
2676         'NOOP_True',
2677         'ProcessTemplate',
2678         'usr',
2679         'print-on-demand',
2680 $$
2681 [%- USE date -%]
2682 [%- SET user = target.0.usr -%]
2683 [%- SET lib = target.0.circ_lib -%]
2684 [%- SET lib_addr = target.0.circ_lib.billing_address -%]
2685 [%- SET hours = lib.hours_of_operation -%]
2686 <div>
2687     <style> li { padding: 8px; margin 5px; }</style>
2688     <div>[% date.format %]</div>
2689     <div>[% lib.name %]</div>
2690     <div>[% lib_addr.street1 %] [% lib_addr.street2 %]</div>
2691     <div>[% lib_addr.city %], [% lib_addr.state %] [% lb_addr.post_code %]</div>
2692     <div>[% lib.phone %]</div>
2693     <br/>
2694
2695     [% user.family_name %], [% user.first_given_name %]
2696     <ol>
2697     [% FOR circ IN target %]
2698         [%-
2699             SET idx = loop.count - 1;
2700             SET udata =  user_data.$idx
2701         -%]
2702         <li>
2703             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2704             <div>Barcode: [% circ.target_copy.barcode %]</div>
2705             [% IF udata.renewal_failure %]
2706                 <div style='color:red;'>Renewal Failed</div>
2707             [% ELSE %]
2708                 <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2709             [% END %]
2710         </li>
2711     [% END %]
2712     </ol>
2713     
2714     <div>
2715         Library Hours
2716         [%- BLOCK format_time; date.format(time _ ' 1/1/1000', format='%I:%M %p'); END -%]
2717         <div>
2718             Monday 
2719             [% PROCESS format_time time = hours.dow_0_open %] 
2720             [% PROCESS format_time time = hours.dow_0_close %] 
2721         </div>
2722         <div>
2723             Tuesday 
2724             [% PROCESS format_time time = hours.dow_1_open %] 
2725             [% PROCESS format_time time = hours.dow_1_close %] 
2726         </div>
2727         <div>
2728             Wednesday 
2729             [% PROCESS format_time time = hours.dow_2_open %] 
2730             [% PROCESS format_time time = hours.dow_2_close %] 
2731         </div>
2732         <div>
2733             Thursday
2734             [% PROCESS format_time time = hours.dow_3_open %] 
2735             [% PROCESS format_time time = hours.dow_3_close %] 
2736         </div>
2737         <div>
2738             Friday
2739             [% PROCESS format_time time = hours.dow_4_open %] 
2740             [% PROCESS format_time time = hours.dow_4_close %] 
2741         </div>
2742         <div>
2743             Saturday
2744             [% PROCESS format_time time = hours.dow_5_open %] 
2745             [% PROCESS format_time time = hours.dow_5_close %] 
2746         </div>
2747         <div>
2748             Sunday 
2749             [% PROCESS format_time time = hours.dow_6_open %] 
2750             [% PROCESS format_time time = hours.dow_6_close %] 
2751         </div>
2752     </div>
2753 </div>
2754 $$
2755 );
2756
2757 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2758     ( 10, 'target_copy'),
2759     ( 10, 'circ_lib.billing_address'),
2760     ( 10, 'circ_lib.hours_of_operation'),
2761     ( 10, 'usr');
2762
2763 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2764     VALUES (
2765         'format.selfcheck.items_out',
2766         'circ',
2767         'Formats items out for self-checkout receipt',
2768         TRUE
2769     );
2770
2771 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2772     VALUES (
2773         11,
2774         TRUE,
2775         1,
2776         'Self-Checkout Items Out Receipt',
2777         'format.selfcheck.items_out',
2778         'NOOP_True',
2779         'ProcessTemplate',
2780         'usr',
2781         'print-on-demand',
2782 $$
2783 [%- USE date -%]
2784 [%- SET user = target.0.usr -%]
2785 <div>
2786     <style> li { padding: 8px; margin 5px; }</style>
2787     <div>[% date.format %]</div>
2788     <br/>
2789
2790     [% user.family_name %], [% user.first_given_name %]
2791     <ol>
2792     [% FOR circ IN target %]
2793         <li>
2794             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2795             <div>Barcode: [% circ.target_copy.barcode %]</div>
2796             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2797         </li>
2798     [% END %]
2799     </ol>
2800 </div>
2801 $$
2802 );
2803
2804
2805 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2806     ( 11, 'target_copy'),
2807     ( 11, 'circ_lib.billing_address'),
2808     ( 11, 'circ_lib.hours_of_operation'),
2809     ( 11, 'usr');
2810
2811 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2812     VALUES (
2813         'format.selfcheck.holds',
2814         'ahr',
2815         'Formats holds for self-checkout receipt',
2816         TRUE
2817     );
2818
2819 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2820     VALUES (
2821         12,
2822         TRUE,
2823         1,
2824         'Self-Checkout Holds Receipt',
2825         'format.selfcheck.holds',
2826         'NOOP_True',
2827         'ProcessTemplate',
2828         'usr',
2829         'print-on-demand',
2830 $$
2831 [%- USE date -%]
2832 [%- SET user = target.0.usr -%]
2833 <div>
2834     <style> li { padding: 8px; margin 5px; }</style>
2835     <div>[% date.format %]</div>
2836     <br/>
2837
2838     [% user.family_name %], [% user.first_given_name %]
2839     <ol>
2840     [% FOR hold IN target %]
2841         [%-
2842             SET idx = loop.count - 1;
2843             SET udata =  user_data.$idx
2844         -%]
2845         <li>
2846             <div>Title: [% hold.bib_rec.bib_record.simple_record.title %]</div>
2847             <div>Author: [% hold.bib_rec.bib_record.simple_record.author %]</div>
2848             <div>Pickup Location: [% hold.pickup_lib.name %]</div>
2849             <div>Status: 
2850                 [%- IF udata.ready -%]
2851                     Ready for pickup
2852                 [% ELSE %]
2853                     #[% udata.queue_position %] of [% udata.potential_copies %] copies.
2854                 [% END %]
2855             </div>
2856         </li>
2857     [% END %]
2858     </ol>
2859 </div>
2860 $$
2861 );
2862
2863
2864 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2865     ( 12, 'bib_rec.bib_record.simple_record'),
2866     ( 12, 'pickup_lib'),
2867     ( 12, 'usr');
2868
2869 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2870     VALUES (
2871         'format.selfcheck.fines',
2872         'au',
2873         'Formats fines for self-checkout receipt',
2874         TRUE
2875     );
2876
2877 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template )
2878     VALUES (
2879         13,
2880         TRUE,
2881         1,
2882         'Self-Checkout Fines Receipt',
2883         'format.selfcheck.fines',
2884         'NOOP_True',
2885         'ProcessTemplate',
2886         'print-on-demand',
2887 $$
2888 [%- USE date -%]
2889 [%- SET user = target -%]
2890 <div>
2891     <style> li { padding: 8px; margin 5px; }</style>
2892     <div>[% date.format %]</div>
2893     <br/>
2894
2895     [% user.family_name %], [% user.first_given_name %]
2896     <ol>
2897     [% FOR xact IN user.open_billable_transactions_summary %]
2898         <li>
2899             <div>Details: 
2900                 [% IF xact.xact_type == 'circulation' %]
2901                     [%- helpers.get_copy_bib_basics(xact.circulation.target_copy).title -%]
2902                 [% ELSE %]
2903                     [%- xact.last_billing_type -%]
2904                 [% END %]
2905             </div>
2906             <div>Total Billed: [% xact.total_owed %]</div>
2907             <div>Total Paid: [% xact.total_paid %]</div>
2908             <div>Balance Owed : [% xact.balance_owed %]</div>
2909         </li>
2910     [% END %]
2911     </ol>
2912 </div>
2913 $$
2914 );
2915
2916 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2917     ( 13, 'open_billable_transactions_summary.circulation' );
2918
2919 INSERT INTO action_trigger.reactor (module,description) VALUES
2920 (   'SendFile',
2921     oils_i18n_gettext(
2922         'SendFile',
2923         '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.',
2924         'atreact',
2925         'description'
2926     )
2927 );
2928
2929 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2930     VALUES (
2931         'format.acqli.html',
2932         'jub',
2933         'Formats lineitem worksheet for titles received',
2934         TRUE
2935     );
2936
2937 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template)
2938     VALUES (
2939         14,
2940         TRUE,
2941         1,
2942         'Lineitem Worksheet',
2943         'format.acqli.html',
2944         'NOOP_True',
2945         'ProcessTemplate',
2946         'print-on-demand',
2947 $$
2948 [%- USE date -%]
2949 [%- SET li = target; -%]
2950 <div class="wrapper">
2951     <div class="summary" style='font-size:110%; font-weight:bold;'>
2952
2953         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
2954         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
2955         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
2956         <div class="lineid">Lineitem ID: [% li.id %]</div>
2957
2958         [% IF li.distribution_formulas.size > 0 %]
2959             [% SET forms = [] %]
2960             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
2961             <div>Distribution Formulas: [% forms.join(',') %]</div>
2962         [% END %]
2963
2964         [% IF li.lineitem_notes.size > 0 %]
2965             Lineitem Notes:
2966             <ul>
2967                 [%- FOR note IN li.lineitem_notes -%]
2968                     <li>
2969                     [% IF note.alert_text %]
2970                         [% note.alert_text.code -%] 
2971                         [% IF note.value -%]
2972                             : [% note.value %]
2973                         [% END %]
2974                     [% ELSE %]
2975                         [% note.value -%] 
2976                     [% END %]
2977                     </li>
2978                 [% END %]
2979             </ul>
2980         [% END %]
2981     </div>
2982     <br/>
2983     <table>
2984         <thead>
2985             <tr>
2986                 <th>Branch</th>
2987                 <th>Barcode</th>
2988                 <th>Call Number</th>
2989                 <th>Fund</th>
2990                 <th>Recd.</th>
2991                 <th>Notes</th>
2992             </tr>
2993         </thead>
2994         <tbody>
2995         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
2996             [% 
2997                 IF copy.eg_copy_id;
2998                     SET copy = copy.eg_copy_id;
2999                     SET cn_label = copy.call_number.label;
3000                 ELSE; 
3001                     SET copy = detail; 
3002                     SET cn_label = detail.cn_label;
3003                 END 
3004             %]
3005             <tr>
3006                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
3007                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
3008                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
3009                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
3010                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
3011                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
3012                 <td style='padding:5px;'>[% detail.note %]</td>
3013             </tr>
3014         [% END %]
3015         </tbody>
3016     </table>
3017 </div>
3018 $$
3019 );
3020
3021
3022 INSERT INTO action_trigger.environment (event_def, path) VALUES
3023     ( 14, 'attributes' ),
3024     ( 14, 'lineitem_details' ),
3025     ( 14, 'lineitem_details.owning_lib' ),
3026     ( 14, 'lineitem_notes' )
3027 ;
3028
3029 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3030         'aur.ordered',
3031         'aur', 
3032         oils_i18n_gettext(
3033             'aur.ordered',
3034             'A patron acquisition request has been marked On-Order.',
3035             'ath',
3036             'description'
3037         ), 
3038         TRUE
3039     ), (
3040         'aur.received', 
3041         'aur', 
3042         oils_i18n_gettext(
3043             'aur.received', 
3044             'A patron acquisition request has been marked Received.',
3045             'ath',
3046             'description'
3047         ),
3048         TRUE
3049     ), (
3050         'aur.cancelled',
3051         'aur',
3052         oils_i18n_gettext(
3053             'aur.cancelled',
3054             'A patron acquisition request has been marked Cancelled.',
3055             'ath',
3056             'description'
3057         ),
3058         TRUE
3059     )
3060 ;
3061
3062 INSERT INTO action_trigger.validator (module,description) VALUES (
3063         'Acq::UserRequestOrdered',
3064         oils_i18n_gettext(
3065             'Acq::UserRequestOrdered',
3066             'Tests to see if the corresponding Line Item has a state of "on-order".',
3067             'atval',
3068             'description'
3069         )
3070     ), (
3071         'Acq::UserRequestReceived',
3072         oils_i18n_gettext(
3073             'Acq::UserRequestReceived',
3074             'Tests to see if the corresponding Line Item has a state of "received".',
3075             'atval',
3076             'description'
3077         )
3078     ), (
3079         'Acq::UserRequestCancelled',
3080         oils_i18n_gettext(
3081             'Acq::UserRequestCancelled',
3082             'Tests to see if the corresponding Line Item has a state of "cancelled".',
3083             'atval',
3084             'description'
3085         )
3086     )
3087 ;
3088
3089 -- What was event_definition #15 in v1.6.1 will be recreated as #20.  This
3090 -- renumbering requires some juggling:
3091 --
3092 -- 1. Update any child rows to point to #20.  These updates will temporarily
3093 -- violate foreign key constraints, but that's okay as long as we create
3094 -- #20 before committing.
3095 --
3096 -- 2. Delete the old #15.
3097 --
3098 -- 3. Insert the new #15.
3099 --
3100 -- 4. Insert #20.
3101 --
3102 -- We could combine steps 2 and 3 into a single update, but that would create
3103 -- additional opportunities for typos, since we already have the insert from
3104 -- an upgrade script.
3105
3106 UPDATE action_trigger.environment
3107 SET event_def = 20
3108 WHERE event_def = 15;
3109
3110 UPDATE action_trigger.event
3111 SET event_def = 20
3112 WHERE event_def = 15;
3113
3114 UPDATE action_trigger.event_params
3115 SET event_def = 20
3116 WHERE event_def = 15;
3117
3118 DELETE FROM action_trigger.event_definition
3119 WHERE id = 15;
3120
3121 INSERT INTO action_trigger.event_definition (
3122         id,
3123         active,
3124         owner,
3125         name,
3126         hook,
3127         validator,
3128         reactor,
3129         template
3130     ) VALUES (
3131         15,
3132         FALSE,
3133         1,
3134         'Email Notice: Patron Acquisition Request marked On-Order.',
3135         'aur.ordered',
3136         'Acq::UserRequestOrdered',
3137         'SendEmail',
3138 $$
3139 [%- USE date -%]
3140 [%- SET li = target.lineitem; -%]
3141 [%- SET user = target.usr -%]
3142 [%- SET title = helpers.get_li_attr("title", "", li.attributes) -%]
3143 [%- SET author = helpers.get_li_attr("author", "", li.attributes) -%]
3144 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) -%]
3145 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) -%]
3146 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3147 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3148
3149 To: [%- params.recipient_email || user.email %]
3150 From: [%- params.sender_email || default_sender %]
3151 Subject: Acquisition Request Notification
3152
3153 Dear [% user.family_name %], [% user.first_given_name %]
3154 Our records indicate the following acquisition request has been placed on order.
3155
3156 Title: [% title %]
3157 [% IF author %]Author: [% author %][% END %]
3158 [% IF edition %]Edition: [% edition %][% END %]
3159 [% IF isbn %]ISBN: [% isbn %][% END %]
3160 [% IF publisher %]Publisher: [% publisher %][% END %]
3161 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3162 Lineitem ID: [% li.id %]
3163 $$
3164     ), (
3165         16,
3166         FALSE,
3167         1,
3168         'Email Notice: Patron Acquisition Request marked Received.',
3169         'aur.received',
3170         'Acq::UserRequestReceived',
3171         'SendEmail',
3172 $$
3173 [%- USE date -%]
3174 [%- SET li = target.lineitem; -%]
3175 [%- SET user = target.usr -%]
3176 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3177 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3178 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3179 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3180 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3181 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3182
3183 To: [%- params.recipient_email || user.email %]
3184 From: [%- params.sender_email || default_sender %]
3185 Subject: Acquisition Request Notification
3186
3187 Dear [% user.family_name %], [% user.first_given_name %]
3188 Our records indicate the materials for the following acquisition request have been received.
3189
3190 Title: [% title %]
3191 [% IF author %]Author: [% author %][% END %]
3192 [% IF edition %]Edition: [% edition %][% END %]
3193 [% IF isbn %]ISBN: [% isbn %][% END %]
3194 [% IF publisher %]Publisher: [% publisher %][% END %]
3195 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3196 Lineitem ID: [% li.id %]
3197 $$
3198     ), (
3199         17,
3200         FALSE,
3201         1,
3202         'Email Notice: Patron Acquisition Request marked Cancelled.',
3203         'aur.cancelled',
3204         'Acq::UserRequestCancelled',
3205         'SendEmail',
3206 $$
3207 [%- USE date -%]
3208 [%- SET li = target.lineitem; -%]
3209 [%- SET user = target.usr -%]
3210 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3211 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3212 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3213 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3214 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3215 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3216
3217 To: [%- params.recipient_email || user.email %]
3218 From: [%- params.sender_email || default_sender %]
3219 Subject: Acquisition Request Notification
3220
3221 Dear [% user.family_name %], [% user.first_given_name %]
3222 Our records indicate the following acquisition request has been cancelled.
3223
3224 Title: [% title %]
3225 [% IF author %]Author: [% author %][% END %]
3226 [% IF edition %]Edition: [% edition %][% END %]
3227 [% IF isbn %]ISBN: [% isbn %][% END %]
3228 [% IF publisher %]Publisher: [% publisher %][% END %]
3229 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3230 Lineitem ID: [% li.id %]
3231 $$
3232     );
3233
3234 INSERT INTO action_trigger.environment (
3235         event_def,
3236         path
3237     ) VALUES 
3238         ( 15, 'lineitem' ),
3239         ( 15, 'lineitem.attributes' ),
3240         ( 15, 'usr' ),
3241
3242         ( 16, 'lineitem' ),
3243         ( 16, 'lineitem.attributes' ),
3244         ( 16, 'usr' ),
3245
3246         ( 17, 'lineitem' ),
3247         ( 17, 'lineitem.attributes' ),
3248         ( 17, 'usr' )
3249     ;
3250
3251 INSERT INTO action_trigger.event_definition
3252 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
3253 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
3254 $$[%- USE date -%]
3255 [%# start JEDI document -%]
3256 [%- BLOCK big_block -%]
3257 {
3258    "recipient":"[% target.provider.san %]",
3259    "sender":"[% target.ordering_agency.mailing_address.san %]",
3260    "body": [{
3261      "ORDERS":[ "order", {
3262         "po_number":[% target.id %],
3263         "date":"[% date.format(date.now, '%Y%m%d') %]",
3264         "buyer":[{
3265             [%- IF target.provider.edi_default.vendcode -%]
3266                 "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]", 
3267                 "id-qualifier": 91
3268             [%- ELSE -%]
3269                 "id":"[% target.ordering_agency.mailing_address.san %]"
3270             [%- END  -%]
3271         }],
3272         "vendor":[ 
3273             [%- # target.provider.name (target.provider.id) -%]
3274             "[% target.provider.san %]",
3275             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
3276         ],
3277         "currency":"[% target.provider.currency_type %]",
3278         "items":[
3279         [% FOR li IN target.lineitems %]
3280         {
3281             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
3282             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
3283                 [% IF isbn.length == 13 -%]
3284                 {"id-qualifier":"EN","id":"[% isbn %]"},
3285                 [% ELSE -%]
3286                 {"id-qualifier":"IB","id":"[% isbn %]"},
3287                 [%- END %]
3288             [% END %]
3289                 {"id-qualifier":"SA","id":"[% li.id %]"}
3290             ],
3291             "price":[% li.estimated_unit_price || '0.00' %],
3292             "desc":[
3293                 {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"}, 
3294                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
3295                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
3296                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
3297             ],
3298             "quantity":[% li.lineitem_details.size %]
3299         }[% UNLESS loop.last %],[% END %]
3300         [%-# TODO: lineitem details (later) -%]
3301         [% END %]
3302         ],
3303         "line_items":[% target.lineitems.size %]
3304      }]  [% # close ORDERS array %]
3305    }]    [% # close  body  array %]
3306 }
3307 [% END %]
3308 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
3309 $$
3310 );
3311
3312 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3313   (23, 'lineitems.attributes'), 
3314   (23, 'lineitems.lineitem_details'), 
3315   (23, 'lineitems.lineitem_notes'), 
3316   (23, 'ordering_agency.mailing_address'), 
3317   (23, 'provider');
3318
3319 UPDATE action_trigger.event_definition SET template = 
3320 $$
3321 [%- USE date -%]
3322 [%-
3323     # find a lineitem attribute by name and optional type
3324     BLOCK get_li_attr;
3325         FOR attr IN li.attributes;
3326             IF attr.attr_name == attr_name;
3327                 IF !attr_type OR attr_type == attr.attr_type;
3328                     attr.attr_value;
3329                     LAST;
3330                 END;
3331             END;
3332         END;
3333     END
3334 -%]
3335
3336 <h2>Purchase Order [% target.id %]</h2>
3337 <br/>
3338 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
3339 <br/>
3340
3341 <style>
3342     table td { padding:5px; border:1px solid #aaa;}
3343     table { width:95%; border-collapse:collapse; }
3344     #vendor-notes { padding:5px; border:1px solid #aaa; }
3345 </style>
3346 <table id='vendor-table'>
3347   <tr>
3348     <td valign='top'>Vendor</td>
3349     <td>
3350       <div>[% target.provider.name %]</div>
3351       <div>[% target.provider.addresses.0.street1 %]</div>
3352       <div>[% target.provider.addresses.0.street2 %]</div>
3353       <div>[% target.provider.addresses.0.city %]</div>
3354       <div>[% target.provider.addresses.0.state %]</div>
3355       <div>[% target.provider.addresses.0.country %]</div>
3356       <div>[% target.provider.addresses.0.post_code %]</div>
3357     </td>
3358     <td valign='top'>Ship to / Bill to</td>
3359     <td>
3360       <div>[% target.ordering_agency.name %]</div>
3361       <div>[% target.ordering_agency.billing_address.street1 %]</div>
3362       <div>[% target.ordering_agency.billing_address.street2 %]</div>
3363       <div>[% target.ordering_agency.billing_address.city %]</div>
3364       <div>[% target.ordering_agency.billing_address.state %]</div>
3365       <div>[% target.ordering_agency.billing_address.country %]</div>
3366       <div>[% target.ordering_agency.billing_address.post_code %]</div>
3367     </td>
3368   </tr>
3369 </table>
3370
3371 <br/><br/>
3372 <fieldset id='vendor-notes'>
3373     <legend>Notes to the Vendor</legend>
3374     <ul>
3375     [% FOR note IN target.notes %]
3376         [% IF note.vendor_public == 't' %]
3377             <li>[% note.value %]</li>
3378         [% END %]
3379     [% END %]
3380     </ul>
3381 </fieldset>
3382 <br/><br/>
3383
3384 <table>
3385   <thead>
3386     <tr>
3387       <th>PO#</th>
3388       <th>ISBN or Item #</th>
3389       <th>Title</th>
3390       <th>Quantity</th>
3391       <th>Unit Price</th>
3392       <th>Line Total</th>
3393       <th>Notes</th>
3394     </tr>
3395   </thead>
3396   <tbody>
3397
3398   [% subtotal = 0 %]
3399   [% FOR li IN target.lineitems %]
3400
3401   <tr>
3402     [% count = li.lineitem_details.size %]
3403     [% price = li.estimated_unit_price %]
3404     [% litotal = (price * count) %]
3405     [% subtotal = subtotal + litotal %]
3406     [% isbn = PROCESS get_li_attr attr_name = 'isbn' %]
3407     [% ident = PROCESS get_li_attr attr_name = 'identifier' %]
3408
3409     <td>[% target.id %]</td>
3410     <td>[% isbn || ident %]</td>
3411     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
3412     <td>[% count %]</td>
3413     <td>[% price %]</td>
3414     <td>[% litotal %]</td>
3415     <td>
3416         <ul>
3417         [% FOR note IN li.lineitem_notes %]
3418             [% IF note.vendor_public == 't' %]
3419                 <li>[% note.value %]</li>
3420             [% END %]
3421         [% END %]
3422         </ul>
3423     </td>
3424   </tr>
3425   [% END %]
3426   <tr>
3427     <td/><td/><td/><td/>
3428     <td>Subtotal</td>
3429     <td>[% subtotal %]</td>
3430   </tr>
3431   </tbody>
3432 </table>
3433
3434 <br/>
3435
3436 Total Line Item Count: [% target.lineitems.size %]
3437 $$
3438 WHERE id = 4;
3439
3440 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3441     (4, 'lineitems.lineitem_notes'),
3442     (4, 'notes');
3443
3444 INSERT INTO action_trigger.environment (event_def, path) VALUES
3445     ( 14, 'lineitem_notes.alert_text' ),
3446     ( 14, 'distribution_formulas.formula' ),
3447     ( 14, 'lineitem_details.fund' ),
3448     ( 14, 'lineitem_details.eg_copy_id' ),
3449     ( 14, 'lineitem_details.eg_copy_id.call_number' )
3450 ;
3451
3452 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3453         'aur.created',
3454         'aur',
3455         oils_i18n_gettext(
3456             'aur.created',
3457             'A patron has made an acquisitions request.',
3458             'ath',
3459             'description'
3460         ),
3461         TRUE
3462     ), (
3463         'aur.rejected',
3464         'aur',
3465         oils_i18n_gettext(
3466             'aur.rejected',
3467             'A patron acquisition request has been rejected.',
3468             'ath',
3469             'description'
3470         ),
3471         TRUE
3472     )
3473 ;
3474
3475 INSERT INTO action_trigger.event_definition (
3476         id,
3477         active,
3478         owner,
3479         name,
3480         hook,
3481         validator,
3482         reactor,
3483         template
3484     ) VALUES (
3485         18,
3486         FALSE,
3487         1,
3488         'Email Notice: Acquisition Request created.',
3489         'aur.created',
3490         'NOOP_True',
3491         'SendEmail',
3492 $$
3493 [%- USE date -%]
3494 [%- SET user = target.usr -%]
3495 [%- SET title = target.title -%]
3496 [%- SET author = target.author -%]
3497 [%- SET isxn = target.isxn -%]
3498 [%- SET publisher = target.publisher -%]
3499 [%- SET pubdate = target.pubdate -%]
3500
3501 To: [%- params.recipient_email || user.email %]
3502 From: [%- params.sender_email || default_sender %]
3503 Subject: Acquisition Request Notification
3504
3505 Dear [% user.family_name %], [% user.first_given_name %]
3506 Our records indicate that you have made the following acquisition request:
3507
3508 Title: [% title %]
3509 [% IF author %]Author: [% author %][% END %]
3510 [% IF edition %]Edition: [% edition %][% END %]
3511 [% IF isbn %]ISXN: [% isxn %][% END %]
3512 [% IF publisher %]Publisher: [% publisher %][% END %]
3513 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3514 $$
3515     ), (
3516         19,
3517         FALSE,
3518         1,
3519         'Email Notice: Acquisition Request Rejected.',
3520         'aur.rejected',
3521         'NOOP_True',
3522         'SendEmail',
3523 $$
3524 [%- USE date -%]
3525 [%- SET user = target.usr -%]
3526 [%- SET title = target.title -%]
3527 [%- SET author = target.author -%]
3528 [%- SET isxn = target.isxn -%]
3529 [%- SET publisher = target.publisher -%]
3530 [%- SET pubdate = target.pubdate -%]
3531 [%- SET cancel_reason = target.cancel_reason.description -%]
3532
3533 To: [%- params.recipient_email || user.email %]
3534 From: [%- params.sender_email || default_sender %]
3535 Subject: Acquisition Request Notification
3536
3537 Dear [% user.family_name %], [% user.first_given_name %]
3538 Our records indicate the following acquisition request has been rejected for this reason: [% cancel_reason %]
3539
3540 Title: [% title %]
3541 [% IF author %]Author: [% author %][% END %]
3542 [% IF edition %]Edition: [% edition %][% END %]
3543 [% IF isbn %]ISBN: [% isbn %][% END %]
3544 [% IF publisher %]Publisher: [% publisher %][% END %]
3545 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3546 $$
3547     );
3548
3549 INSERT INTO action_trigger.environment (
3550         event_def,
3551         path
3552     ) VALUES 
3553         ( 18, 'usr' ),
3554         ( 19, 'usr' ),
3555         ( 19, 'cancel_reason' )
3556     ;
3557
3558 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, template) 
3559     VALUES (20, 'f', 1, 'Password reset request notification', 'password.reset_request', 'NOOP_True', 'SendEmail', '00:00:01',
3560 $$
3561 [%- USE date -%]
3562 [%- user = target.usr -%]
3563 To: [%- params.recipient_email || user.email %]
3564 From: [%- params.sender_email || user.home_ou.email || default_sender %]
3565 Subject: [% user.home_ou.name %]: library account password reset request
3566   
3567 You have received this message because you, or somebody else, requested a reset
3568 of your library system password. If you did not request a reset of your library
3569 system password, just ignore this message and your current password will
3570 continue to work.
3571
3572 If you did request a reset of your library system password, please perform
3573 the following steps to continue the process of resetting your password:
3574
3575 1. Open the following link in a web browser: https://[% params.hostname %]/opac/password/[% params.locale || 'en-US' %]/[% target.uuid %]
3576 The browser displays a password reset form.
3577
3578 2. Enter your new password in the password reset form in the browser. You must
3579 enter the password twice to ensure that you do not make a mistake. If the
3580 passwords match, you will then be able to log in to your library system account
3581 with the new password.
3582
3583 $$);
3584
3585 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3586     VALUES (
3587         'format.acqcle.html',
3588         'acqcle',
3589         'Formats claim events into a voucher',
3590         TRUE
3591     );
3592
3593 INSERT INTO action_trigger.event_definition (
3594         id, active, owner, name, hook, group_field,
3595         validator, reactor, granularity, template
3596     ) VALUES (
3597         21,
3598         TRUE,
3599         1,
3600         'Claim Voucher',
3601         'format.acqcle.html',
3602         'claim',
3603         'NOOP_True',
3604         'ProcessTemplate',
3605         'print-on-demand',
3606 $$
3607 [%- USE date -%]
3608 [%- SET claim = target.0.claim -%]
3609 <!-- This will need refined/prettified. -->
3610 <div class="acq-claim-voucher">
3611     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
3612     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
3613     <ul>
3614         [% FOR event IN target %]
3615         <li>
3616             Event type: [% event.type.code %]
3617             [% IF event.type.library_initiated %](Library initiated)[% END %]
3618             <br />
3619             Event date: [% event.event_date %]<br />
3620             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
3621             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
3622             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
3623             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
3624             [% event.claim.lineitem_detail.fund.code %]
3625             ([% event.claim.lineitem_detail.fund.year %])
3626         </li>
3627         [% END %]
3628     </ul>
3629 </div>
3630 $$
3631 );
3632
3633 INSERT INTO action_trigger.environment (event_def, path) VALUES
3634     (21, 'claim'),
3635     (21, 'claim.type'),
3636     (21, 'claim.lineitem_detail'),
3637     (21, 'claim.lineitem_detail.fund'),
3638     (21, 'claim.lineitem_detail.lineitem.attributes'),
3639     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
3640     (21, 'creator'),
3641     (21, 'type')
3642 ;
3643
3644 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3645     VALUES (
3646         'format.acqinv.html',
3647         'acqinv',
3648         'Formats invoices into a voucher',
3649         TRUE
3650     );
3651
3652 INSERT INTO action_trigger.event_definition (
3653         id, active, owner, name, hook,
3654         validator, reactor, granularity, template
3655     ) VALUES (
3656         22,
3657         TRUE,
3658         1,
3659         'Invoice',
3660         'format.acqinv.html',
3661         'NOOP_True',
3662         'ProcessTemplate',
3663         'print-on-demand',
3664 $$
3665 [% FILTER collapse %]
3666 [%- SET invoice = target -%]
3667 <!-- This lacks totals, info about funds (for invoice entries,
3668     funds are per-LID!), and general refinement -->
3669 <div class="acq-invoice-voucher">
3670     <h1>Invoice</h1>
3671     <div>
3672         <strong>No.</strong> [% invoice.inv_ident %]
3673         [% IF invoice.inv_type %]
3674             / <strong>Type:</strong>[% invoice.inv_type %]
3675         [% END %]
3676     </div>
3677     <div>
3678         <dl>
3679             [% BLOCK ent_with_address %]
3680             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3681             <dd>
3682                 [% IF ent.addresses.0 %]
3683                     [% SET addr = ent.addresses.0 %]
3684                     [% addr.street1 %]<br />
3685                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3686                     [% addr.city %],
3687                     [% IF addr.county %] [% addr.county %], [% END %]
3688                     [% IF addr.state %] [% addr.state %] [% END %]
3689                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3690                     [% IF addr.country %] [% addr.country %] [% END %]
3691                 [% END %]
3692                 <p>
3693                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3694                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3695                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3696                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3697                 </p>
3698             </dd>
3699             [% END %]
3700             [% INCLUDE ent_with_address
3701                 ent = invoice.provider
3702                 ent_label = "Provider" %]
3703             [% INCLUDE ent_with_address
3704                 ent = invoice.shipper
3705                 ent_label = "Shipper" %]
3706             <dt>Receiver</dt>
3707             <dd>
3708                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3709             </dd>
3710             <dt>Received</dt>
3711             <dd>
3712                 [% helpers.format_date(invoice.recv_date) %] by
3713                 [% invoice.recv_method %]
3714             </dd>
3715             [% IF invoice.note %]
3716                 <dt>Note</dt>
3717                 <dd>
3718                     [% invoice.note %]
3719                 </dd>
3720             [% END %]
3721         </dl>
3722     </div>
3723     <ul>
3724         [% FOR entry IN invoice.entries %]
3725             <li>
3726                 [% IF entry.lineitem %]
3727                     Title: [% helpers.get_li_attr(
3728                         "title", "", entry.lineitem.attributes
3729                     ) %]<br />
3730                     Author: [% helpers.get_li_attr(
3731                         "author", "", entry.lineitem.attributes
3732                     ) %]
3733                 [% END %]
3734                 [% IF entry.purchase_order %]
3735                     (PO: [% entry.purchase_order.name %])
3736                 [% END %]<br />
3737                 Invoice item count: [% entry.inv_item_count %]
3738                 [% IF entry.phys_item_count %]
3739                     / Physical item count: [% entry.phys_item_count %]
3740                 [% END %]
3741                 <br />
3742                 [% IF entry.cost_billed %]
3743                     Cost billed: [% entry.cost_billed %]
3744                     [% IF entry.billed_per_item %](per item)[% END %]
3745                     <br />
3746                 [% END %]
3747                 [% IF entry.actual_cost %]
3748                     Actual cost: [% entry.actual_cost %]<br />
3749                 [% END %]
3750                 [% IF entry.amount_paid %]
3751                     Amount paid: [% entry.amount_paid %]<br />
3752                 [% END %]
3753                 [% IF entry.note %]Note: [% entry.note %][% END %]
3754             </li>
3755         [% END %]
3756         [% FOR item IN invoice.items %]
3757             <li>
3758                 [% IF item.inv_item_type %]
3759                     Item Type: [% item.inv_item_type %]<br />
3760                 [% END %]
3761                 [% IF item.title %]Title/Description:
3762                     [% item.title %]<br />
3763                 [% END %]
3764                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3765                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3766                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3767                 [% IF item.cost_billed %]
3768                     Cost billed: [% item.cost_billed %]<br />
3769                 [% END %]
3770                 [% IF item.actual_cost %]
3771                     Actual cost: [% item.actual_cost %]<br />
3772                 [% END %]
3773                 [% IF item.amount_paid %]
3774                     Amount paid: [% item.amount_paid %]<br />
3775                 [% END %]
3776             </li>
3777         [% END %]
3778     </ul>
3779 </div>
3780 [% END %]
3781 $$
3782 );
3783
3784 INSERT INTO action_trigger.environment (event_def, path) VALUES
3785     (22, 'provider'),
3786     (22, 'provider.addresses'),
3787     (22, 'shipper'),
3788     (22, 'shipper.addresses'),
3789     (22, 'receiver'),
3790     (22, 'entries'),
3791     (22, 'entries.purchase_order'),
3792     (22, 'entries.lineitem'),
3793     (22, 'entries.lineitem.attributes'),
3794     (22, 'items')
3795 ;
3796
3797 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3798   (23, 'provider.edi_default');
3799
3800 INSERT INTO action_trigger.validator (module, description) 
3801     VALUES (
3802         'Acq::PurchaseOrderEDIRequired',
3803         oils_i18n_gettext(
3804             'Acq::PurchaseOrderEDIRequired',
3805             'Purchase order is delivered via EDI',
3806             'atval',
3807             'description'
3808         )
3809     );
3810
3811 INSERT INTO action_trigger.reactor (module, description)
3812     VALUES (
3813         'GeneratePurchaseOrderJEDI',
3814         oils_i18n_gettext(
3815             'GeneratePurchaseOrderJEDI',
3816             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
3817             'atreact',
3818             'description'
3819         )
3820     );
3821
3822 UPDATE action_trigger.hook 
3823     SET 
3824         key = 'acqpo.activated', 
3825         passive = FALSE,
3826         description = oils_i18n_gettext(
3827             'acqpo.activated',
3828             'Purchase order was activated',
3829             'ath',
3830             'description'
3831         )
3832     WHERE key = 'format.po.jedi';
3833
3834 -- We just changed a key in action_trigger.hook.  Now redirect any
3835 -- child rows to point to the new key.  (There probably aren't any;
3836 -- this is just a precaution against possible local modifications.)
3837
3838 UPDATE action_trigger.event_definition
3839 SET hook = 'acqpo.activated'
3840 WHERE hook = 'format.po.jedi';
3841
3842 INSERT INTO action_trigger.reactor (module, description) VALUES (
3843     'AstCall', 'Possibly place a phone call with Asterisk'
3844 );
3845
3846 INSERT INTO
3847     action_trigger.event_definition (
3848         id, active, owner, name, hook, validator, reactor,
3849         cleanup_success, cleanup_failure, delay, delay_field, group_field,
3850         max_delay, granularity, usr_field, opt_in_setting, template
3851     ) VALUES (
3852         24,
3853         FALSE,
3854         1,
3855         'Telephone Overdue Notice',
3856         'checkout.due', 'NOOP_True', 'AstCall',
3857         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
3858         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
3859         $$
3860 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
3861 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
3862 Channel: [% channel_prefix %]/[% country %][% phone %]
3863 Context: overdue-test
3864 MaxRetries: 1
3865 RetryTime: 60
3866 WaitTime: 30
3867 Extension: 10
3868 Archive: 1
3869 Set: eg_user_id=[% target.0.usr.id %]
3870 Set: items=[% target.size %]
3871 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
3872 $$
3873     );
3874
3875 INSERT INTO
3876     action_trigger.environment (id, event_def, path)
3877     VALUES
3878         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
3879         (DEFAULT, 24, 'usr')
3880     ;
3881
3882 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3883         'circ.format.history.email',
3884         'circ', 
3885         oils_i18n_gettext(
3886             'circ.format.history.email',
3887             'An email has been requested for a circ history.',
3888             'ath',
3889             'description'
3890         ), 
3891         FALSE
3892     )
3893     ,(
3894         'circ.format.history.print',
3895         'circ', 
3896         oils_i18n_gettext(
3897             'circ.format.history.print',
3898             'A circ history needs to be formatted for printing.',
3899             'ath',
3900             'description'
3901         ), 
3902         FALSE
3903     )
3904     ,(
3905         'ahr.format.history.email',
3906         'ahr', 
3907         oils_i18n_gettext(
3908             'ahr.format.history.email',
3909             'An email has been requested for a hold request history.',
3910             'ath',
3911             'description'
3912         ), 
3913         FALSE
3914     )
3915     ,(
3916         'ahr.format.history.print',
3917         'ahr', 
3918         oils_i18n_gettext(
3919             'ahr.format.history.print',
3920             'A hold request history needs to be formatted for printing.',
3921             'ath',
3922             'description'
3923         ), 
3924         FALSE
3925     )
3926
3927 ;
3928
3929 INSERT INTO action_trigger.event_definition (
3930         id,
3931         active,
3932         owner,
3933         name,
3934         hook,
3935         validator,
3936         reactor,
3937         group_field,
3938         granularity,
3939         template
3940     ) VALUES (
3941         25,
3942         TRUE,
3943         1,
3944         'circ.history.email',
3945         'circ.format.history.email',
3946         'NOOP_True',
3947         'SendEmail',
3948         'usr',
3949         NULL,
3950 $$
3951 [%- USE date -%]
3952 [%- SET user = target.0.usr -%]
3953 To: [%- params.recipient_email || user.email %]
3954 From: [%- params.sender_email || default_sender %]
3955 Subject: Circulation History
3956
3957     [% FOR circ IN target %]
3958             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
3959             Barcode: [% circ.target_copy.barcode %]
3960             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
3961             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
3962             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
3963     [% END %]
3964 $$
3965     )
3966     ,(
3967         26,
3968         TRUE,
3969         1,
3970         'circ.history.print',
3971         'circ.format.history.print',
3972         'NOOP_True',
3973         'ProcessTemplate',
3974         'usr',
3975         'print-on-demand',
3976 $$
3977 [%- USE date -%]
3978 <div>
3979     <style> li { padding: 8px; margin 5px; }</style>
3980     <div>[% date.format %]</div>
3981     <br/>
3982
3983     [% user.family_name %], [% user.first_given_name %]
3984     <ol>
3985     [% FOR circ IN target %]
3986         <li>
3987             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
3988             <div>Barcode: [% circ.target_copy.barcode %]</div>
3989             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
3990             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
3991             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
3992         </li>
3993     [% END %]
3994     </ol>
3995 </div>
3996 $$
3997     )
3998     ,(
3999         27,
4000         TRUE,
4001         1,
4002         'ahr.history.email',
4003         'ahr.format.history.email',
4004         'NOOP_True',
4005         'SendEmail',
4006         'usr',
4007         NULL,
4008 $$
4009 [%- USE date -%]
4010 [%- SET user = target.0.usr -%]
4011 To: [%- params.recipient_email || user.email %]
4012 From: [%- params.sender_email || default_sender %]
4013 Subject: Hold Request History
4014
4015     [% FOR hold IN target %]
4016             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
4017             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
4018             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
4019     [% END %]
4020 $$
4021     )
4022     ,(
4023         28,
4024         TRUE,
4025         1,
4026         'ahr.history.print',
4027         'ahr.format.history.print',
4028         'NOOP_True',
4029         'ProcessTemplate',
4030         'usr',
4031         'print-on-demand',
4032 $$
4033 [%- USE date -%]
4034 <div>
4035     <style> li { padding: 8px; margin 5px; }</style>
4036     <div>[% date.format %]</div>
4037     <br/>
4038
4039     [% user.family_name %], [% user.first_given_name %]
4040     <ol>
4041     [% FOR hold IN target %]
4042         <li>
4043             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
4044             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
4045             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
4046         </li>
4047     [% END %]
4048     </ol>
4049 </div>
4050 $$
4051     )
4052
4053 ;
4054
4055 INSERT INTO action_trigger.environment (
4056         event_def,
4057         path
4058     ) VALUES 
4059          ( 25, 'target_copy')
4060         ,( 25, 'usr' )
4061         ,( 26, 'target_copy' )
4062         ,( 26, 'usr' )
4063         ,( 27, 'current_copy' )
4064         ,( 27, 'usr' )
4065         ,( 28, 'current_copy' )
4066         ,( 28, 'usr' )
4067 ;
4068
4069 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4070         'money.format.payment_receipt.email',
4071         'mp', 
4072         oils_i18n_gettext(
4073             'money.format.payment_receipt.email',
4074             'An email has been requested for a payment receipt.',
4075             'ath',
4076             'description'
4077         ), 
4078         FALSE
4079     )
4080     ,(
4081         'money.format.payment_receipt.print',
4082         'mp', 
4083         oils_i18n_gettext(
4084             'money.format.payment_receipt.print',
4085             'A payment receipt needs to be formatted for printing.',
4086             'ath',
4087             'description'
4088         ), 
4089         FALSE
4090     )
4091 ;
4092
4093 INSERT INTO action_trigger.event_definition (
4094         id,
4095         active,
4096         owner,
4097         name,
4098         hook,
4099         validator,
4100         reactor,
4101         group_field,
4102         granularity,
4103         template
4104     ) VALUES (
4105         29,
4106         TRUE,
4107         1,
4108         'money.payment_receipt.email',
4109         'money.format.payment_receipt.email',
4110         'NOOP_True',
4111         'SendEmail',
4112         'xact.usr',
4113         NULL,
4114 $$
4115 [%- USE date -%]
4116 [%- SET user = target.0.xact.usr -%]
4117 To: [%- params.recipient_email || user.email %]
4118 From: [%- params.sender_email || default_sender %]
4119 Subject: Payment Receipt
4120
4121 [% date.format -%]
4122 [%- SET xact_mp_hash = {} -%]
4123 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
4124     [%- SET xact_id = mp.xact.id -%]
4125     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
4126     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
4127 [%- END -%]
4128 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
4129     [%- SET xact = xact_mp_hash.$xact_id.xact %]
4130 Transaction ID: [% xact_id %]
4131     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4132     [% ELSE %]Miscellaneous
4133     [% END %]
4134     Line item billings:
4135         [%- SET mb_type_hash = {} -%]
4136         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
4137             [%- IF mb.voided == 'f' -%]
4138                 [%- SET mb_type = mb.btype.id -%]
4139                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
4140                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
4141                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
4142                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
4143                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
4144             [%- END -%]
4145         [%- END -%]
4146         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
4147             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
4148                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4149                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4150             [%- ELSE -%][%# all other billings show individually %]
4151                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
4152                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4153                 [% END %]
4154             [% END %]
4155         [% END %]
4156     Line item payments:
4157         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4158             Payment ID: [% mp.id %]
4159                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4160                     [% CASE "cash_payment" %]cash
4161                     [% CASE "check_payment" %]check
4162                     [% CASE "credit_card_payment" %]credit card (
4163                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4164                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4165                         [% cc_chunks.last -%]
4166                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4167                     )
4168                     [% CASE "credit_payment" %]credit
4169                     [% CASE "forgive_payment" %]forgiveness
4170                     [% CASE "goods_payment" %]goods
4171                     [% CASE "work_payment" %]work
4172                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
4173         [% END %]
4174 [% END %]
4175 $$
4176     )
4177     ,(
4178         30,
4179         TRUE,
4180         1,
4181         'money.payment_receipt.print',
4182         'money.format.payment_receipt.print',
4183         'NOOP_True',
4184         'ProcessTemplate',
4185         'xact.usr',
4186         'print-on-demand',
4187 $$
4188 [%- USE date -%][%- SET user = target.0.xact.usr -%]
4189 <div style="li { padding: 8px; margin 5px; }">
4190     <div>[% date.format %]</div><br/>
4191     <ol>
4192     [% SET xact_mp_hash = {} %]
4193     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
4194         [% SET xact_id = mp.xact.id %]
4195         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
4196         [% xact_mp_hash.$xact_id.payments.push(mp) %]
4197     [% END %]
4198     [% FOR xact_id IN xact_mp_hash.keys.sort %]
4199         [% SET xact = xact_mp_hash.$xact_id.xact %]
4200         <li>Transaction ID: [% xact_id %]
4201             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4202             [% ELSE %]Miscellaneous
4203             [% END %]
4204             Line item billings:<ol>
4205                 [% SET mb_type_hash = {} %]
4206                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
4207                     [% IF mb.voided == 'f' %]
4208                         [% SET mb_type = mb.btype.id %]
4209                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
4210                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
4211                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
4212                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
4213                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
4214                     [% END %]
4215                 [% END %]
4216                 [% FOR mb_type IN mb_type_hash.keys.sort %]
4217                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
4218                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4219                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4220                     [% ELSE %][%# all other billings show individually %]
4221                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
4222                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4223                         [% END %]
4224                     [% END %]</li>
4225                 [% END %]
4226             </ol>
4227             Line item payments:<ol>
4228                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4229                     <li>Payment ID: [% mp.id %]
4230                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4231                             [% CASE "cash_payment" %]cash
4232                             [% CASE "check_payment" %]check
4233                             [% CASE "credit_card_payment" %]credit card (
4234                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4235                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4236                                 [% cc_chunks.last -%]
4237                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4238                             )
4239                             [% CASE "credit_payment" %]credit
4240                             [% CASE "forgive_payment" %]forgiveness
4241                             [% CASE "goods_payment" %]goods
4242                             [% CASE "work_payment" %]work
4243                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
4244                     </li>
4245                 [% END %]
4246             </ol>
4247         </li>
4248     [% END %]
4249     </ol>
4250 </div>
4251 $$
4252     )
4253 ;
4254
4255 INSERT INTO action_trigger.environment (
4256         event_def,
4257         path
4258     ) VALUES -- for fleshing mp objects
4259          ( 29, 'xact')
4260         ,( 29, 'xact.usr')
4261         ,( 29, 'xact.grocery' )
4262         ,( 29, 'xact.circulation' )
4263         ,( 29, 'xact.summary' )
4264         ,( 30, 'xact')
4265         ,( 30, 'xact.usr')
4266         ,( 30, 'xact.grocery' )
4267         ,( 30, 'xact.circulation' )
4268         ,( 30, 'xact.summary' )
4269 ;
4270
4271 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
4272     'DeleteTempBiblioBucket',
4273     oils_i18n_gettext(
4274         'DeleteTempBiblioBucket',
4275         'Deletes a cbreb object used as a target if it has a btype of "temp"',
4276         'atclean',
4277         'description'
4278     )
4279 );
4280
4281 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4282         'biblio.format.record_entry.email',
4283         'cbreb', 
4284         oils_i18n_gettext(
4285             'biblio.format.record_entry.email',
4286             'An email has been requested for one or more biblio record entries.',
4287             'ath',
4288             'description'
4289         ), 
4290         FALSE
4291     )
4292     ,(
4293         'biblio.format.record_entry.print',
4294         'cbreb', 
4295         oils_i18n_gettext(
4296             'biblio.format.record_entry.print',
4297             'One or more biblio record entries need to be formatted for printing.',
4298             'ath',
4299             'description'
4300         ), 
4301         FALSE
4302     )
4303 ;
4304
4305 INSERT INTO action_trigger.event_definition (
4306         id,
4307         active,
4308         owner,
4309         name,
4310         hook,
4311         validator,
4312         reactor,
4313         cleanup_success,
4314         cleanup_failure,
4315         group_field,
4316         granularity,
4317         template
4318     ) VALUES (
4319         31,
4320         TRUE,
4321         1,
4322         'biblio.record_entry.email',
4323         'biblio.format.record_entry.email',
4324         'NOOP_True',
4325         'SendEmail',
4326         'DeleteTempBiblioBucket',
4327         'DeleteTempBiblioBucket',
4328         'owner',
4329         NULL,
4330 $$
4331 [%- USE date -%]
4332 [%- SET user = target.0.owner -%]
4333 To: [%- params.recipient_email || user.email %]
4334 From: [%- params.sender_email || default_sender %]
4335 Subject: Bibliographic Records
4336
4337     [% FOR cbreb IN target %]
4338     [% FOR cbrebi IN cbreb.items %]
4339         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
4340         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
4341         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
4342         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4343
4344     [% END %]
4345     [% END %]
4346 $$
4347     )
4348     ,(
4349         32,
4350         TRUE,
4351         1,
4352         'biblio.record_entry.print',
4353         'biblio.format.record_entry.print',
4354         'NOOP_True',
4355         'ProcessTemplate',
4356         'DeleteTempBiblioBucket',
4357         'DeleteTempBiblioBucket',
4358         'owner',
4359         'print-on-demand',
4360 $$
4361 [%- USE date -%]
4362 <div>
4363     <style> li { padding: 8px; margin 5px; }</style>
4364     <ol>
4365     [% FOR cbreb IN target %]
4366     [% FOR cbrebi IN cbreb.items %]
4367         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
4368             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
4369             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
4370             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4371         </li>
4372     [% END %]
4373     [% END %]
4374     </ol>
4375 </div>
4376 $$
4377     )
4378 ;
4379
4380 INSERT INTO action_trigger.environment (
4381         event_def,
4382         path
4383     ) VALUES -- for fleshing cbreb objects
4384          ( 31, 'owner' )
4385         ,( 31, 'items' )
4386         ,( 31, 'items.target_biblio_record_entry' )
4387         ,( 31, 'items.target_biblio_record_entry.simple_record' )
4388         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
4389         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
4390         ,( 31, 'items.target_biblio_record_entry.notes' )
4391         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
4392         ,( 32, 'owner' )
4393         ,( 32, 'items' )
4394         ,( 32, 'items.target_biblio_record_entry' )
4395         ,( 32, 'items.target_biblio_record_entry.simple_record' )
4396         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
4397         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
4398         ,( 32, 'items.target_biblio_record_entry.notes' )
4399         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
4400 ;
4401
4402 INSERT INTO action_trigger.environment (
4403         event_def,
4404         path
4405     ) VALUES -- for fleshing mp objects
4406          ( 29, 'credit_card_payment')
4407         ,( 29, 'xact.billings')
4408         ,( 29, 'xact.billings.btype')
4409         ,( 30, 'credit_card_payment')
4410         ,( 30, 'xact.billings')
4411         ,( 30, 'xact.billings.btype')
4412 ;
4413
4414 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
4415     (   'circ.format.missing_pieces.slip.print',
4416         'circ', 
4417         oils_i18n_gettext(
4418             'circ.format.missing_pieces.slip.print',
4419             'A missing pieces slip needs to be formatted for printing.',
4420             'ath',
4421             'description'
4422         ), 
4423         FALSE
4424     )
4425     ,(  'circ.format.missing_pieces.letter.print',
4426         'circ', 
4427         oils_i18n_gettext(
4428             'circ.format.missing_pieces.letter.print',
4429             'A missing pieces patron letter needs to be formatted for printing.',
4430             'ath',
4431             'description'
4432         ), 
4433         FALSE
4434     )
4435 ;
4436
4437 INSERT INTO action_trigger.event_definition (
4438         id,
4439         active,
4440         owner,
4441         name,
4442         hook,
4443         validator,
4444         reactor,
4445         group_field,
4446         granularity,
4447         template
4448     ) VALUES (
4449         33,
4450         TRUE,
4451         1,
4452         'circ.missing_pieces.slip.print',
4453         'circ.format.missing_pieces.slip.print',
4454         'NOOP_True',
4455         'ProcessTemplate',
4456         'usr',
4457         'print-on-demand',
4458 $$
4459 [%- USE date -%]
4460 [%- SET user = target.0.usr -%]
4461 <div style="li { padding: 8px; margin 5px; }">
4462     <div>[% date.format %]</div><br/>
4463     Missing pieces for:
4464     <ol>
4465     [% FOR circ IN target %]
4466         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
4467             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4468         </li>
4469     [% END %]
4470     </ol>
4471 </div>
4472 $$
4473     )
4474     ,(
4475         34,
4476         TRUE,
4477         1,
4478         'circ.missing_pieces.letter.print',
4479         'circ.format.missing_pieces.letter.print',
4480         'NOOP_True',
4481         'ProcessTemplate',
4482         'usr',
4483         'print-on-demand',
4484 $$
4485 [%- USE date -%]
4486 [%- SET user = target.0.usr -%]
4487 [% date.format %]
4488 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
4489
4490 We are missing pieces for the following returned items:
4491 [% FOR circ IN target %]
4492 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
4493 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4494 [% END %]
4495
4496 Please return these pieces as soon as possible.
4497
4498 Thanks!
4499
4500 Library Staff
4501 $$
4502     )
4503 ;
4504
4505 INSERT INTO action_trigger.environment (
4506         event_def,
4507         path
4508     ) VALUES -- for fleshing circ objects
4509          ( 33, 'usr')
4510         ,( 33, 'target_copy')
4511         ,( 33, 'target_copy.circ_lib')
4512         ,( 33, 'target_copy.circ_lib.mailing_address')
4513         ,( 33, 'target_copy.circ_lib.billing_address')
4514         ,( 33, 'target_copy.call_number')
4515         ,( 33, 'target_copy.call_number.owning_lib')
4516         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
4517         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
4518         ,( 33, 'circ_lib')
4519         ,( 33, 'circ_lib.mailing_address')
4520         ,( 33, 'circ_lib.billing_address')
4521         ,( 34, 'usr')
4522         ,( 34, 'target_copy')
4523         ,( 34, 'target_copy.circ_lib')
4524         ,( 34, 'target_copy.circ_lib.mailing_address')
4525         ,( 34, 'target_copy.circ_lib.billing_address')
4526         ,( 34, 'target_copy.call_number')
4527         ,( 34, 'target_copy.call_number.owning_lib')
4528         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
4529         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
4530         ,( 34, 'circ_lib')
4531         ,( 34, 'circ_lib.mailing_address')
4532         ,( 34, 'circ_lib.billing_address')
4533 ;
4534
4535 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
4536     VALUES (   
4537         'ahr.format.pull_list',
4538         'ahr', 
4539         oils_i18n_gettext(
4540             'ahr.format.pull_list',
4541             'Format holds pull list for printing',
4542             'ath',
4543             'description'
4544         ), 
4545         FALSE
4546     );
4547
4548 INSERT INTO action_trigger.event_definition (
4549         id,
4550         active,
4551         owner,
4552         name,
4553         hook,
4554         validator,
4555         reactor,
4556         group_field,
4557         granularity,
4558         template
4559     ) VALUES (
4560         35,
4561         TRUE,
4562         1,
4563         'Holds Pull List',
4564         'ahr.format.pull_list',
4565         'NOOP_True',
4566         'ProcessTemplate',
4567         'pickup_lib',
4568         'print-on-demand',
4569 $$
4570 [%- USE date -%]
4571 <style>
4572     table { border-collapse: collapse; } 
4573     td { padding: 5px; border-bottom: 1px solid #888; } 
4574     th { font-weight: bold; }
4575 </style>
4576 [% 
4577     # Sort the holds into copy-location buckets
4578     # In the main print loop, sort each bucket by callnumber before printing
4579     SET holds_list = [];
4580     SET loc_data = [];
4581     SET current_location = target.0.current_copy.location.id;
4582     FOR hold IN target;
4583         IF current_location != hold.current_copy.location.id;
4584             SET current_location = hold.current_copy.location.id;
4585             holds_list.push(loc_data);
4586             SET loc_data = [];
4587         END;
4588         SET hold_data = {
4589             'hold' => hold,
4590             'callnumber' => hold.current_copy.call_number.label
4591         };
4592         loc_data.push(hold_data);
4593     END;
4594     holds_list.push(loc_data)
4595 %]
4596 <table>
4597     <thead>
4598         <tr>
4599             <th>Title</th>
4600             <th>Author</th>
4601             <th>Shelving Location</th>
4602             <th>Call Number</th>
4603             <th>Barcode</th>
4604             <th>Patron</th>
4605         </tr>
4606     </thead>
4607     <tbody>
4608     [% FOR loc_data IN holds_list  %]
4609         [% FOR hold_data IN loc_data.sort('callnumber') %]
4610             [% 
4611                 SET hold = hold_data.hold;
4612                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
4613             %]
4614             <tr>
4615                 <td>[% copy_data.title | truncate %]</td>
4616                 <td>[% copy_data.author | truncate %]</td>
4617                 <td>[% hold.current_copy.location.name %]</td>
4618                 <td>[% hold.current_copy.call_number.label %]</td>
4619                 <td>[% hold.current_copy.barcode %]</td>
4620                 <td>[% hold.usr.card.barcode %]</td>
4621             </tr>
4622         [% END %]
4623     [% END %]
4624     <tbody>
4625 </table>
4626 $$
4627 );
4628
4629 INSERT INTO action_trigger.environment (
4630         event_def,
4631         path
4632     ) VALUES
4633         (35, 'current_copy.location'),
4634         (35, 'current_copy.call_number'),
4635         (35, 'usr.card'),
4636         (35, 'pickup_lib')
4637 ;
4638
4639 INSERT INTO action_trigger.validator (module, description) VALUES ( 
4640     'HoldIsCancelled', 
4641     oils_i18n_gettext( 
4642         'HoldIsCancelled', 
4643         'Check whether a hold request is cancelled.', 
4644         'atval', 
4645         'description' 
4646     ) 
4647 );
4648
4649 -- Create the query schema, and the tables and views therein
4650
4651 DROP SCHEMA IF EXISTS sql CASCADE;
4652 DROP SCHEMA IF EXISTS query CASCADE;
4653
4654 CREATE SCHEMA query;
4655
4656 CREATE TABLE query.datatype (
4657         id              SERIAL            PRIMARY KEY,
4658         datatype_name   TEXT              NOT NULL UNIQUE,
4659         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
4660         is_composite    BOOL              NOT NULL DEFAULT FALSE,
4661         CONSTRAINT qdt_comp_not_num CHECK
4662         ( is_numeric IS FALSE OR is_composite IS FALSE )
4663 );
4664
4665 -- Define the most common datatypes in query.datatype.  Note that none of
4666 -- these stock datatypes specifies a width or precision.
4667
4668 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
4669 -- room for more stock datatypes if we ever want to add them.
4670
4671 SELECT setval( 'query.datatype_id_seq', 1000 );
4672
4673 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4674   VALUES (1, 'SMALLINT', true);
4675  
4676 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4677   VALUES (2, 'INTEGER', true);
4678  
4679 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4680   VALUES (3, 'BIGINT', true);
4681  
4682 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4683   VALUES (4, 'DECIMAL', true);
4684  
4685 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4686   VALUES (5, 'NUMERIC', true);
4687  
4688 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4689   VALUES (6, 'REAL', true);
4690  
4691 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4692   VALUES (7, 'DOUBLE PRECISION', true);
4693  
4694 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4695   VALUES (8, 'SERIAL', true);
4696  
4697 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4698   VALUES (9, 'BIGSERIAL', true);
4699  
4700 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4701   VALUES (10, 'MONEY', false);
4702  
4703 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4704   VALUES (11, 'VARCHAR', false);
4705  
4706 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4707   VALUES (12, 'CHAR', false);
4708  
4709 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4710   VALUES (13, 'TEXT', false);
4711  
4712 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4713   VALUES (14, '"char"', false);
4714  
4715 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4716   VALUES (15, 'NAME', false);
4717  
4718 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4719   VALUES (16, 'BYTEA', false);
4720  
4721 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4722   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
4723  
4724 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4725   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
4726  
4727 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4728   VALUES (19, 'DATE', false);
4729  
4730 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4731   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
4732  
4733 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4734   VALUES (21, 'TIME WITH TIME ZONE', false);
4735  
4736 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4737   VALUES (22, 'INTERVAL', false);
4738  
4739 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4740   VALUES (23, 'BOOLEAN', false);
4741  
4742 CREATE TABLE query.subfield (
4743         id              SERIAL            PRIMARY KEY,
4744         composite_type  INT               NOT NULL
4745                                           REFERENCES query.datatype(id)
4746                                           ON DELETE CASCADE
4747                                           DEFERRABLE INITIALLY DEFERRED,
4748         seq_no          INT               NOT NULL
4749                                           CONSTRAINT qsf_pos_seq_no
4750                                           CHECK( seq_no > 0 ),
4751         subfield_type   INT               NOT NULL
4752                                           REFERENCES query.datatype(id)
4753                                           DEFERRABLE INITIALLY DEFERRED,
4754         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
4755 );
4756
4757 CREATE TABLE query.function_sig (
4758         id              SERIAL            PRIMARY KEY,
4759         function_name   TEXT              NOT NULL,
4760         return_type     INT               REFERENCES query.datatype(id)
4761                                           DEFERRABLE INITIALLY DEFERRED,
4762         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
4763         CONSTRAINT qfd_rtn_or_aggr CHECK
4764         ( return_type IS NULL OR is_aggregate = FALSE )
4765 );
4766
4767 CREATE INDEX query_function_sig_name_idx 
4768         ON query.function_sig (function_name);
4769
4770 CREATE TABLE query.function_param_def (
4771         id              SERIAL            PRIMARY KEY,
4772         function_id     INT               NOT NULL
4773                                           REFERENCES query.function_sig( id )
4774                                           ON DELETE CASCADE
4775                                           DEFERRABLE INITIALLY DEFERRED,
4776         seq_no          INT               NOT NULL
4777                                           CONSTRAINT qfpd_pos_seq_no CHECK
4778                                           ( seq_no > 0 ),
4779         datatype        INT               NOT NULL
4780                                           REFERENCES query.datatype( id )
4781                                           DEFERRABLE INITIALLY DEFERRED,
4782         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
4783 );
4784
4785 CREATE TABLE  query.stored_query (
4786         id            SERIAL         PRIMARY KEY,
4787         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
4788                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
4789         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
4790         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
4791         from_clause   INT            , --REFERENCES query.from_clause
4792                                      --DEFERRABLE INITIALLY DEFERRED,
4793         where_clause  INT            , --REFERENCES query.expression
4794                                      --DEFERRABLE INITIALLY DEFERRED,
4795         having_clause INT            , --REFERENCES query.expression
4796                                      --DEFERRABLE INITIALLY DEFERRED
4797         limit_count   INT            , --REFERENCES query.expression( id )
4798                                      --DEFERRABLE INITIALLY DEFERRED,
4799         offset_count  INT            --REFERENCES query.expression( id )
4800                                      --DEFERRABLE INITIALLY DEFERRED
4801 );
4802
4803 -- (Foreign keys to be defined later after other tables are created)
4804
4805 CREATE TABLE query.query_sequence (
4806         id              SERIAL            PRIMARY KEY,
4807         parent_query    INT               NOT NULL
4808                                           REFERENCES query.stored_query
4809                                                                           ON DELETE CASCADE
4810                                                                           DEFERRABLE INITIALLY DEFERRED,
4811         seq_no          INT               NOT NULL,
4812         child_query     INT               NOT NULL
4813                                           REFERENCES query.stored_query
4814                                                                           ON DELETE CASCADE
4815                                                                           DEFERRABLE INITIALLY DEFERRED,
4816         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
4817 );
4818
4819 CREATE TABLE query.bind_variable (
4820         name          TEXT             PRIMARY KEY,
4821         type          TEXT             NOT NULL
4822                                            CONSTRAINT bind_variable_type CHECK
4823                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
4824         description   TEXT             NOT NULL,
4825         default_value TEXT,            -- to be encoded in JSON
4826         label         TEXT             NOT NULL
4827 );
4828
4829 CREATE TABLE query.expression (
4830         id            SERIAL        PRIMARY KEY,
4831         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
4832                                     ( type IN (
4833                                     'xbet',    -- between
4834                                     'xbind',   -- bind variable
4835                                     'xbool',   -- boolean
4836                                     'xcase',   -- case
4837                                     'xcast',   -- cast
4838                                     'xcol',    -- column
4839                                     'xex',     -- exists
4840                                     'xfunc',   -- function
4841                                     'xin',     -- in
4842                                     'xisnull', -- is null
4843                                     'xnull',   -- null
4844                                     'xnum',    -- number
4845                                     'xop',     -- operator
4846                                     'xser',    -- series
4847                                     'xstr',    -- string
4848                                     'xsubq'    -- subquery
4849                                                                 ) ),
4850         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
4851         parent_expr   INT           REFERENCES query.expression
4852                                     ON DELETE CASCADE
4853                                     DEFERRABLE INITIALLY DEFERRED,
4854         seq_no        INT           NOT NULL DEFAULT 1,
4855         literal       TEXT,
4856         table_alias   TEXT,
4857         column_name   TEXT,
4858         left_operand  INT           REFERENCES query.expression
4859                                     DEFERRABLE INITIALLY DEFERRED,
4860         operator      TEXT,
4861         right_operand INT           REFERENCES query.expression
4862                                     DEFERRABLE INITIALLY DEFERRED,
4863         function_id   INT           REFERENCES query.function_sig
4864                                     DEFERRABLE INITIALLY DEFERRED,
4865         subquery      INT           REFERENCES query.stored_query
4866                                     DEFERRABLE INITIALLY DEFERRED,
4867         cast_type     INT           REFERENCES query.datatype
4868                                     DEFERRABLE INITIALLY DEFERRED,
4869         negate        BOOL          NOT NULL DEFAULT FALSE,
4870         bind_variable TEXT          REFERENCES query.bind_variable
4871                                         DEFERRABLE INITIALLY DEFERRED
4872 );
4873
4874 CREATE UNIQUE INDEX query_expr_parent_seq
4875         ON query.expression( parent_expr, seq_no )
4876         WHERE parent_expr IS NOT NULL;
4877
4878 -- Due to some circular references, the following foreign key definitions
4879 -- had to be deferred until query.expression existed:
4880
4881 ALTER TABLE query.stored_query
4882         ADD FOREIGN KEY ( where_clause )
4883         REFERENCES query.expression( id )
4884         DEFERRABLE INITIALLY DEFERRED;
4885
4886 ALTER TABLE query.stored_query
4887         ADD FOREIGN KEY ( having_clause )
4888         REFERENCES query.expression( id )
4889         DEFERRABLE INITIALLY DEFERRED;
4890
4891 ALTER TABLE query.stored_query
4892     ADD FOREIGN KEY ( limit_count )
4893     REFERENCES query.expression( id )
4894     DEFERRABLE INITIALLY DEFERRED;
4895
4896 ALTER TABLE query.stored_query
4897     ADD FOREIGN KEY ( offset_count )
4898     REFERENCES query.expression( id )
4899     DEFERRABLE INITIALLY DEFERRED;
4900
4901 CREATE TABLE query.case_branch (
4902         id            SERIAL        PRIMARY KEY,
4903         parent_expr   INT           NOT NULL REFERENCES query.expression
4904                                     ON DELETE CASCADE
4905                                     DEFERRABLE INITIALLY DEFERRED,
4906         seq_no        INT           NOT NULL,
4907         condition     INT           REFERENCES query.expression
4908                                     DEFERRABLE INITIALLY DEFERRED,
4909         result        INT           NOT NULL REFERENCES query.expression
4910                                     DEFERRABLE INITIALLY DEFERRED,
4911         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
4912 );
4913
4914 CREATE TABLE query.from_relation (
4915         id               SERIAL        PRIMARY KEY,
4916         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
4917                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
4918         table_name       TEXT,
4919         class_name       TEXT,
4920         subquery         INT           REFERENCES query.stored_query,
4921         function_call    INT           REFERENCES query.expression,
4922         table_alias      TEXT,
4923         parent_relation  INT           REFERENCES query.from_relation
4924                                        ON DELETE CASCADE
4925                                        DEFERRABLE INITIALLY DEFERRED,
4926         seq_no           INT           NOT NULL DEFAULT 1,
4927         join_type        TEXT          CONSTRAINT good_join_type CHECK (
4928                                            join_type IS NULL OR join_type IN
4929                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
4930                                        ),
4931         on_clause        INT           REFERENCES query.expression
4932                                        DEFERRABLE INITIALLY DEFERRED,
4933         CONSTRAINT join_or_core CHECK (
4934         ( parent_relation IS NULL AND join_type IS NULL
4935           AND on_clause IS NULL )
4936         OR
4937         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
4938           AND on_clause IS NOT NULL )
4939         )
4940 );
4941
4942 CREATE UNIQUE INDEX from_parent_seq
4943         ON query.from_relation( parent_relation, seq_no )
4944         WHERE parent_relation IS NOT NULL;
4945
4946 -- The following foreign key had to be deferred until
4947 -- query.from_relation existed
4948
4949 ALTER TABLE query.stored_query
4950         ADD FOREIGN KEY (from_clause)
4951         REFERENCES query.from_relation
4952         DEFERRABLE INITIALLY DEFERRED;
4953
4954 CREATE TABLE query.record_column (
4955         id            SERIAL            PRIMARY KEY,
4956         from_relation INT               NOT NULL REFERENCES query.from_relation
4957                                         ON DELETE CASCADE
4958                                         DEFERRABLE INITIALLY DEFERRED,
4959         seq_no        INT               NOT NULL,
4960         column_name   TEXT              NOT NULL,
4961         column_type   INT               NOT NULL REFERENCES query.datatype
4962                                         ON DELETE CASCADE
4963                                                                         DEFERRABLE INITIALLY DEFERRED,
4964         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
4965 );
4966
4967 CREATE TABLE query.select_item (
4968         id               SERIAL         PRIMARY KEY,
4969         stored_query     INT            NOT NULL REFERENCES query.stored_query
4970                                         ON DELETE CASCADE
4971                                         DEFERRABLE INITIALLY DEFERRED,
4972         seq_no           INT            NOT NULL,
4973         expression       INT            NOT NULL REFERENCES query.expression
4974                                         DEFERRABLE INITIALLY DEFERRED,
4975         column_alias     TEXT,
4976         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
4977         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
4978 );
4979
4980 CREATE TABLE query.order_by_item (
4981         id               SERIAL         PRIMARY KEY,
4982         stored_query     INT            NOT NULL REFERENCES query.stored_query
4983                                         ON DELETE CASCADE
4984                                         DEFERRABLE INITIALLY DEFERRED,
4985         seq_no           INT            NOT NULL,
4986         expression       INT            NOT NULL REFERENCES query.expression
4987                                         ON DELETE CASCADE
4988                                         DEFERRABLE INITIALLY DEFERRED,
4989         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
4990 );
4991
4992 ------------------------------------------------------------
4993 -- Create updatable views for different kinds of expressions
4994 ------------------------------------------------------------
4995
4996 -- Create updatable view for BETWEEN expressions
4997
4998 CREATE OR REPLACE VIEW query.expr_xbet AS
4999     SELECT
5000                 id,
5001                 parenthesize,
5002                 parent_expr,
5003                 seq_no,
5004                 left_operand,
5005                 negate
5006     FROM
5007         query.expression
5008     WHERE
5009         type = 'xbet';
5010
5011 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
5012     ON INSERT TO query.expr_xbet
5013     DO INSTEAD
5014     INSERT INTO query.expression (
5015                 id,
5016                 type,
5017                 parenthesize,
5018                 parent_expr,
5019                 seq_no,
5020                 left_operand,
5021                 negate
5022     ) VALUES (
5023         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5024         'xbet',
5025         COALESCE(NEW.parenthesize, FALSE),
5026         NEW.parent_expr,
5027         COALESCE(NEW.seq_no, 1),
5028                 NEW.left_operand,
5029                 COALESCE(NEW.negate, false)
5030     );
5031
5032 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
5033     ON UPDATE TO query.expr_xbet
5034     DO INSTEAD
5035     UPDATE query.expression SET
5036         id = NEW.id,
5037         parenthesize = NEW.parenthesize,
5038         parent_expr = NEW.parent_expr,
5039         seq_no = NEW.seq_no,
5040                 left_operand = NEW.left_operand,
5041                 negate = NEW.negate
5042     WHERE
5043         id = OLD.id;
5044
5045 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
5046     ON DELETE TO query.expr_xbet
5047     DO INSTEAD
5048     DELETE FROM query.expression WHERE id = OLD.id;
5049
5050 -- Create updatable view for bind variable expressions
5051
5052 CREATE OR REPLACE VIEW query.expr_xbind AS
5053     SELECT
5054                 id,
5055                 parenthesize,
5056                 parent_expr,
5057                 seq_no,
5058                 bind_variable
5059     FROM
5060         query.expression
5061     WHERE
5062         type = 'xbind';
5063
5064 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
5065     ON INSERT TO query.expr_xbind
5066     DO INSTEAD
5067     INSERT INTO query.expression (
5068                 id,
5069                 type,
5070                 parenthesize,
5071                 parent_expr,
5072                 seq_no,
5073                 bind_variable
5074     ) VALUES (
5075         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5076         'xbind',
5077         COALESCE(NEW.parenthesize, FALSE),
5078         NEW.parent_expr,
5079         COALESCE(NEW.seq_no, 1),
5080                 NEW.bind_variable
5081     );
5082
5083 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
5084     ON UPDATE TO query.expr_xbind
5085     DO INSTEAD
5086     UPDATE query.expression SET
5087         id = NEW.id,
5088         parenthesize = NEW.parenthesize,
5089         parent_expr = NEW.parent_expr,
5090         seq_no = NEW.seq_no,
5091                 bind_variable = NEW.bind_variable
5092     WHERE
5093         id = OLD.id;
5094
5095 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
5096     ON DELETE TO query.expr_xbind
5097     DO INSTEAD
5098     DELETE FROM query.expression WHERE id = OLD.id;
5099
5100 -- Create updatable view for boolean expressions
5101
5102 CREATE OR REPLACE VIEW query.expr_xbool AS
5103     SELECT
5104                 id,
5105                 parenthesize,
5106                 parent_expr,
5107                 seq_no,
5108                 literal,
5109                 negate
5110     FROM
5111         query.expression
5112     WHERE
5113         type = 'xbool';
5114
5115 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
5116     ON INSERT TO query.expr_xbool
5117     DO INSTEAD
5118     INSERT INTO query.expression (
5119                 id,
5120                 type,
5121                 parenthesize,
5122                 parent_expr,
5123                 seq_no,
5124                 literal,
5125                 negate
5126     ) VALUES (
5127         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5128         'xbool',
5129         COALESCE(NEW.parenthesize, FALSE),
5130         NEW.parent_expr,
5131         COALESCE(NEW.seq_no, 1),
5132         NEW.literal,
5133                 COALESCE(NEW.negate, false)
5134     );
5135
5136 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
5137     ON UPDATE TO query.expr_xbool
5138     DO INSTEAD
5139     UPDATE query.expression SET
5140         id = NEW.id,
5141         parenthesize = NEW.parenthesize,
5142         parent_expr = NEW.parent_expr,
5143         seq_no = NEW.seq_no,
5144         literal = NEW.literal,
5145                 negate = NEW.negate
5146     WHERE
5147         id = OLD.id;
5148
5149 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
5150     ON DELETE TO query.expr_xbool
5151     DO INSTEAD
5152     DELETE FROM query.expression WHERE id = OLD.id;
5153
5154 -- Create updatable view for CASE expressions
5155
5156 CREATE OR REPLACE VIEW query.expr_xcase AS
5157     SELECT
5158                 id,
5159                 parenthesize,
5160                 parent_expr,
5161                 seq_no,
5162                 left_operand,
5163                 negate
5164     FROM
5165         query.expression
5166     WHERE
5167         type = 'xcase';
5168
5169 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
5170     ON INSERT TO query.expr_xcase
5171     DO INSTEAD
5172     INSERT INTO query.expression (
5173                 id,
5174                 type,
5175                 parenthesize,
5176                 parent_expr,
5177                 seq_no,
5178                 left_operand,
5179                 negate
5180     ) VALUES (
5181         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5182         'xcase',
5183         COALESCE(NEW.parenthesize, FALSE),
5184         NEW.parent_expr,
5185         COALESCE(NEW.seq_no, 1),
5186                 NEW.left_operand,
5187                 COALESCE(NEW.negate, false)
5188     );
5189
5190 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
5191     ON UPDATE TO query.expr_xcase
5192     DO INSTEAD
5193     UPDATE query.expression SET
5194         id = NEW.id,
5195         parenthesize = NEW.parenthesize,
5196         parent_expr = NEW.parent_expr,
5197         seq_no = NEW.seq_no,
5198                 left_operand = NEW.left_operand,
5199                 negate = NEW.negate
5200     WHERE
5201         id = OLD.id;
5202
5203 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
5204     ON DELETE TO query.expr_xcase
5205     DO INSTEAD
5206     DELETE FROM query.expression WHERE id = OLD.id;
5207
5208 -- Create updatable view for cast expressions
5209
5210 CREATE OR REPLACE VIEW query.expr_xcast AS
5211     SELECT
5212                 id,
5213                 parenthesize,
5214                 parent_expr,
5215                 seq_no,
5216                 left_operand,
5217                 cast_type,
5218                 negate
5219     FROM
5220         query.expression
5221     WHERE
5222         type = 'xcast';
5223
5224 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
5225     ON INSERT TO query.expr_xcast
5226     DO INSTEAD
5227     INSERT INTO query.expression (
5228         id,
5229         type,
5230         parenthesize,
5231         parent_expr,
5232         seq_no,
5233         left_operand,
5234         cast_type,
5235         negate
5236     ) VALUES (
5237         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5238         'xcast',
5239         COALESCE(NEW.parenthesize, FALSE),
5240         NEW.parent_expr,
5241         COALESCE(NEW.seq_no, 1),
5242         NEW.left_operand,
5243         NEW.cast_type,
5244         COALESCE(NEW.negate, false)
5245     );
5246
5247 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
5248     ON UPDATE TO query.expr_xcast
5249     DO INSTEAD
5250     UPDATE query.expression SET
5251         id = NEW.id,
5252         parenthesize = NEW.parenthesize,
5253         parent_expr = NEW.parent_expr,
5254         seq_no = NEW.seq_no,
5255                 left_operand = NEW.left_operand,
5256                 cast_type = NEW.cast_type,
5257                 negate = NEW.negate
5258     WHERE
5259         id = OLD.id;
5260
5261 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
5262     ON DELETE TO query.expr_xcast
5263     DO INSTEAD
5264     DELETE FROM query.expression WHERE id = OLD.id;
5265
5266 -- Create updatable view for column expressions
5267
5268 CREATE OR REPLACE VIEW query.expr_xcol AS
5269     SELECT
5270                 id,
5271                 parenthesize,
5272                 parent_expr,
5273                 seq_no,
5274                 table_alias,
5275                 column_name,
5276                 negate
5277     FROM
5278         query.expression
5279     WHERE
5280         type = 'xcol';
5281
5282 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
5283     ON INSERT TO query.expr_xcol
5284     DO INSTEAD
5285     INSERT INTO query.expression (
5286                 id,
5287                 type,
5288                 parenthesize,
5289                 parent_expr,
5290                 seq_no,
5291                 table_alias,
5292                 column_name,
5293                 negate
5294     ) VALUES (
5295         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5296         'xcol',
5297         COALESCE(NEW.parenthesize, FALSE),
5298         NEW.parent_expr,
5299         COALESCE(NEW.seq_no, 1),
5300                 NEW.table_alias,
5301                 NEW.column_name,
5302                 COALESCE(NEW.negate, false)
5303     );
5304
5305 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
5306     ON UPDATE TO query.expr_xcol
5307     DO INSTEAD
5308     UPDATE query.expression SET
5309         id = NEW.id,
5310         parenthesize = NEW.parenthesize,
5311         parent_expr = NEW.parent_expr,
5312         seq_no = NEW.seq_no,
5313                 table_alias = NEW.table_alias,
5314                 column_name = NEW.column_name,
5315                 negate = NEW.negate
5316     WHERE
5317         id = OLD.id;
5318
5319 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
5320     ON DELETE TO query.expr_xcol
5321     DO INSTEAD
5322     DELETE FROM query.expression WHERE id = OLD.id;
5323
5324 -- Create updatable view for EXISTS expressions
5325
5326 CREATE OR REPLACE VIEW query.expr_xex AS
5327     SELECT
5328                 id,
5329                 parenthesize,
5330                 parent_expr,
5331                 seq_no,
5332                 subquery,
5333                 negate
5334     FROM
5335         query.expression
5336     WHERE
5337         type = 'xex';
5338
5339 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
5340     ON INSERT TO query.expr_xex
5341     DO INSTEAD
5342     INSERT INTO query.expression (
5343                 id,
5344                 type,
5345                 parenthesize,
5346                 parent_expr,
5347                 seq_no,
5348                 subquery,
5349                 negate
5350     ) VALUES (
5351         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5352         'xex',
5353         COALESCE(NEW.parenthesize, FALSE),
5354         NEW.parent_expr,
5355         COALESCE(NEW.seq_no, 1),
5356                 NEW.subquery,
5357                 COALESCE(NEW.negate, false)
5358     );
5359
5360 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
5361     ON UPDATE TO query.expr_xex
5362     DO INSTEAD
5363     UPDATE query.expression SET
5364         id = NEW.id,
5365         parenthesize = NEW.parenthesize,
5366         parent_expr = NEW.parent_expr,
5367         seq_no = NEW.seq_no,
5368                 subquery = NEW.subquery,
5369                 negate = NEW.negate
5370     WHERE
5371         id = OLD.id;
5372
5373 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
5374     ON DELETE TO query.expr_xex
5375     DO INSTEAD
5376     DELETE FROM query.expression WHERE id = OLD.id;
5377
5378 -- Create updatable view for function call expressions
5379
5380 CREATE OR REPLACE VIEW query.expr_xfunc AS
5381     SELECT
5382         id,
5383         parenthesize,
5384         parent_expr,
5385         seq_no,
5386         column_name,
5387         function_id,
5388         negate
5389     FROM
5390         query.expression
5391     WHERE
5392         type = 'xfunc';
5393
5394 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
5395     ON INSERT TO query.expr_xfunc
5396     DO INSTEAD
5397     INSERT INTO query.expression (
5398         id,
5399         type,
5400         parenthesize,
5401         parent_expr,
5402         seq_no,
5403         column_name,
5404         function_id,
5405         negate
5406     ) VALUES (
5407         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5408         'xfunc',
5409         COALESCE(NEW.parenthesize, FALSE),
5410         NEW.parent_expr,
5411         COALESCE(NEW.seq_no, 1),
5412         NEW.column_name,
5413         NEW.function_id,
5414         COALESCE(NEW.negate, false)
5415     );
5416
5417 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
5418     ON UPDATE TO query.expr_xfunc
5419     DO INSTEAD
5420     UPDATE query.expression SET
5421         id = NEW.id,
5422         parenthesize = NEW.parenthesize,
5423         parent_expr = NEW.parent_expr,
5424         seq_no = NEW.seq_no,
5425         column_name = NEW.column_name,
5426         function_id = NEW.function_id,
5427         negate = NEW.negate
5428     WHERE
5429         id = OLD.id;
5430
5431 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
5432     ON DELETE TO query.expr_xfunc
5433     DO INSTEAD
5434     DELETE FROM query.expression WHERE id = OLD.id;
5435
5436 -- Create updatable view for IN expressions
5437
5438 CREATE OR REPLACE VIEW query.expr_xin AS
5439     SELECT
5440                 id,
5441                 parenthesize,
5442                 parent_expr,
5443                 seq_no,
5444                 left_operand,
5445                 subquery,
5446                 negate
5447     FROM
5448         query.expression
5449     WHERE
5450         type = 'xin';
5451
5452 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
5453     ON INSERT TO query.expr_xin
5454     DO INSTEAD
5455     INSERT INTO query.expression (
5456                 id,
5457                 type,
5458                 parenthesize,
5459                 parent_expr,
5460                 seq_no,
5461                 left_operand,
5462                 subquery,
5463                 negate
5464     ) VALUES (
5465         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5466         'xin',
5467         COALESCE(NEW.parenthesize, FALSE),
5468         NEW.parent_expr,
5469         COALESCE(NEW.seq_no, 1),
5470                 NEW.left_operand,
5471                 NEW.subquery,
5472                 COALESCE(NEW.negate, false)
5473     );
5474
5475 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
5476     ON UPDATE TO query.expr_xin
5477     DO INSTEAD
5478     UPDATE query.expression SET
5479         id = NEW.id,
5480         parenthesize = NEW.parenthesize,
5481         parent_expr = NEW.parent_expr,
5482         seq_no = NEW.seq_no,
5483                 left_operand = NEW.left_operand,
5484                 subquery = NEW.subquery,
5485                 negate = NEW.negate
5486     WHERE
5487         id = OLD.id;
5488
5489 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
5490     ON DELETE TO query.expr_xin
5491     DO INSTEAD
5492     DELETE FROM query.expression WHERE id = OLD.id;
5493
5494 -- Create updatable view for IS NULL expressions
5495
5496 CREATE OR REPLACE VIEW query.expr_xisnull AS
5497     SELECT
5498                 id,
5499                 parenthesize,
5500                 parent_expr,
5501                 seq_no,
5502                 left_operand,
5503                 negate
5504     FROM
5505         query.expression
5506     WHERE
5507         type = 'xisnull';
5508
5509 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
5510     ON INSERT TO query.expr_xisnull
5511     DO INSTEAD
5512     INSERT INTO query.expression (
5513                 id,
5514                 type,
5515                 parenthesize,
5516                 parent_expr,
5517                 seq_no,
5518                 left_operand,
5519                 negate
5520     ) VALUES (
5521         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5522         'xisnull',
5523         COALESCE(NEW.parenthesize, FALSE),
5524         NEW.parent_expr,
5525         COALESCE(NEW.seq_no, 1),
5526                 NEW.left_operand,
5527                 COALESCE(NEW.negate, false)
5528     );
5529
5530 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
5531     ON UPDATE TO query.expr_xisnull
5532     DO INSTEAD
5533     UPDATE query.expression SET
5534         id = NEW.id,
5535         parenthesize = NEW.parenthesize,
5536         parent_expr = NEW.parent_expr,
5537         seq_no = NEW.seq_no,
5538                 left_operand = NEW.left_operand,
5539                 negate = NEW.negate
5540     WHERE
5541         id = OLD.id;
5542
5543 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
5544     ON DELETE TO query.expr_xisnull
5545     DO INSTEAD
5546     DELETE FROM query.expression WHERE id = OLD.id;
5547
5548 -- Create updatable view for NULL expressions
5549
5550 CREATE OR REPLACE VIEW query.expr_xnull AS
5551     SELECT
5552                 id,
5553                 parenthesize,
5554                 parent_expr,
5555                 seq_no,
5556                 negate
5557     FROM
5558         query.expression
5559     WHERE
5560         type = 'xnull';
5561
5562 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
5563     ON INSERT TO query.expr_xnull
5564     DO INSTEAD
5565     INSERT INTO query.expression (
5566                 id,
5567                 type,
5568                 parenthesize,
5569                 parent_expr,
5570                 seq_no,
5571                 negate
5572     ) VALUES (
5573         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5574         'xnull',
5575         COALESCE(NEW.parenthesize, FALSE),
5576         NEW.parent_expr,
5577         COALESCE(NEW.seq_no, 1),
5578                 COALESCE(NEW.negate, false)
5579     );
5580
5581 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
5582     ON UPDATE TO query.expr_xnull
5583     DO INSTEAD
5584     UPDATE query.expression SET
5585         id = NEW.id,
5586         parenthesize = NEW.parenthesize,
5587         parent_expr = NEW.parent_expr,
5588         seq_no = NEW.seq_no,
5589                 negate = NEW.negate
5590     WHERE
5591         id = OLD.id;
5592
5593 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
5594     ON DELETE TO query.expr_xnull
5595     DO INSTEAD
5596     DELETE FROM query.expression WHERE id = OLD.id;
5597
5598 -- Create updatable view for numeric literal expressions
5599
5600 CREATE OR REPLACE VIEW query.expr_xnum AS
5601     SELECT
5602                 id,
5603                 parenthesize,
5604                 parent_expr,
5605                 seq_no,
5606                 literal
5607     FROM
5608         query.expression
5609     WHERE
5610         type = 'xnum';
5611
5612 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
5613     ON INSERT TO query.expr_xnum
5614     DO INSTEAD
5615     INSERT INTO query.expression (
5616                 id,
5617                 type,
5618                 parenthesize,
5619                 parent_expr,
5620                 seq_no,
5621                 literal
5622     ) VALUES (
5623         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5624         'xnum',
5625         COALESCE(NEW.parenthesize, FALSE),
5626         NEW.parent_expr,
5627         COALESCE(NEW.seq_no, 1),
5628         NEW.literal
5629     );
5630
5631 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
5632     ON UPDATE TO query.expr_xnum
5633     DO INSTEAD
5634     UPDATE query.expression SET
5635         id = NEW.id,
5636         parenthesize = NEW.parenthesize,
5637         parent_expr = NEW.parent_expr,
5638         seq_no = NEW.seq_no,
5639         literal = NEW.literal
5640     WHERE
5641         id = OLD.id;
5642
5643 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
5644     ON DELETE TO query.expr_xnum
5645     DO INSTEAD
5646     DELETE FROM query.expression WHERE id = OLD.id;
5647
5648 -- Create updatable view for operator expressions
5649
5650 CREATE OR REPLACE VIEW query.expr_xop AS
5651     SELECT
5652                 id,
5653                 parenthesize,
5654                 parent_expr,
5655                 seq_no,
5656                 left_operand,
5657                 operator,
5658                 right_operand,
5659                 negate
5660     FROM
5661         query.expression
5662     WHERE
5663         type = 'xop';
5664
5665 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
5666     ON INSERT TO query.expr_xop
5667     DO INSTEAD
5668     INSERT INTO query.expression (
5669                 id,
5670                 type,
5671                 parenthesize,
5672                 parent_expr,
5673                 seq_no,
5674                 left_operand,
5675                 operator,
5676                 right_operand,
5677                 negate
5678     ) VALUES (
5679         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5680         'xop',
5681         COALESCE(NEW.parenthesize, FALSE),
5682         NEW.parent_expr,
5683         COALESCE(NEW.seq_no, 1),
5684                 NEW.left_operand,
5685                 NEW.operator,
5686                 NEW.right_operand,
5687                 COALESCE(NEW.negate, false)
5688     );
5689
5690 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
5691     ON UPDATE TO query.expr_xop
5692     DO INSTEAD
5693     UPDATE query.expression SET
5694         id = NEW.id,
5695         parenthesize = NEW.parenthesize,
5696         parent_expr = NEW.parent_expr,
5697         seq_no = NEW.seq_no,
5698                 left_operand = NEW.left_operand,
5699                 operator = NEW.operator,
5700                 right_operand = NEW.right_operand,
5701                 negate = NEW.negate
5702     WHERE
5703         id = OLD.id;
5704
5705 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
5706     ON DELETE TO query.expr_xop
5707     DO INSTEAD
5708     DELETE FROM query.expression WHERE id = OLD.id;
5709
5710 -- Create updatable view for series expressions
5711 -- i.e. series of expressions separated by operators
5712
5713 CREATE OR REPLACE VIEW query.expr_xser AS
5714     SELECT
5715                 id,
5716                 parenthesize,
5717                 parent_expr,
5718                 seq_no,
5719                 operator,
5720                 negate
5721     FROM
5722         query.expression
5723     WHERE
5724         type = 'xser';
5725
5726 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
5727     ON INSERT TO query.expr_xser
5728     DO INSTEAD
5729     INSERT INTO query.expression (
5730                 id,
5731                 type,
5732                 parenthesize,
5733                 parent_expr,
5734                 seq_no,
5735                 operator,
5736                 negate
5737     ) VALUES (
5738         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5739         'xser',
5740         COALESCE(NEW.parenthesize, FALSE),
5741         NEW.parent_expr,
5742         COALESCE(NEW.seq_no, 1),
5743                 NEW.operator,
5744                 COALESCE(NEW.negate, false)
5745     );
5746
5747 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
5748     ON UPDATE TO query.expr_xser
5749     DO INSTEAD
5750     UPDATE query.expression SET
5751         id = NEW.id,
5752         parenthesize = NEW.parenthesize,
5753         parent_expr = NEW.parent_expr,
5754         seq_no = NEW.seq_no,
5755                 operator = NEW.operator,
5756                 negate = NEW.negate
5757     WHERE
5758         id = OLD.id;
5759
5760 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
5761     ON DELETE TO query.expr_xser
5762     DO INSTEAD
5763     DELETE FROM query.expression WHERE id = OLD.id;
5764
5765 -- Create updatable view for string literal expressions
5766
5767 CREATE OR REPLACE VIEW query.expr_xstr AS
5768     SELECT
5769         id,
5770         parenthesize,
5771         parent_expr,
5772         seq_no,
5773         literal
5774     FROM
5775         query.expression
5776     WHERE
5777         type = 'xstr';
5778
5779 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
5780     ON INSERT TO query.expr_xstr
5781     DO INSTEAD
5782     INSERT INTO query.expression (
5783         id,
5784         type,
5785         parenthesize,
5786         parent_expr,
5787         seq_no,
5788         literal
5789     ) VALUES (
5790         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5791         'xstr',
5792         COALESCE(NEW.parenthesize, FALSE),
5793         NEW.parent_expr,
5794         COALESCE(NEW.seq_no, 1),
5795         NEW.literal
5796     );
5797
5798 CREATE OR REPLACE RULE query_expr_string_update_rule AS
5799     ON UPDATE TO query.expr_xstr
5800     DO INSTEAD
5801     UPDATE query.expression SET
5802         id = NEW.id,
5803         parenthesize = NEW.parenthesize,
5804         parent_expr = NEW.parent_expr,
5805         seq_no = NEW.seq_no,
5806         literal = NEW.literal
5807     WHERE
5808         id = OLD.id;
5809
5810 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
5811     ON DELETE TO query.expr_xstr
5812     DO INSTEAD
5813     DELETE FROM query.expression WHERE id = OLD.id;
5814
5815 -- Create updatable view for subquery expressions
5816
5817 CREATE OR REPLACE VIEW query.expr_xsubq AS
5818     SELECT
5819                 id,
5820                 parenthesize,
5821                 parent_expr,
5822                 seq_no,
5823                 subquery,
5824                 negate
5825     FROM
5826         query.expression
5827     WHERE
5828         type = 'xsubq';
5829
5830 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
5831     ON INSERT TO query.expr_xsubq
5832     DO INSTEAD
5833     INSERT INTO query.expression (
5834                 id,
5835                 type,
5836                 parenthesize,
5837                 parent_expr,
5838                 seq_no,
5839                 subquery,
5840                 negate
5841     ) VALUES (
5842         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5843         'xsubq',
5844         COALESCE(NEW.parenthesize, FALSE),
5845         NEW.parent_expr,
5846         COALESCE(NEW.seq_no, 1),
5847                 NEW.subquery,
5848                 COALESCE(NEW.negate, false)
5849     );
5850
5851 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
5852     ON UPDATE TO query.expr_xsubq
5853     DO INSTEAD
5854     UPDATE query.expression SET
5855         id = NEW.id,
5856         parenthesize = NEW.parenthesize,
5857         parent_expr = NEW.parent_expr,
5858         seq_no = NEW.seq_no,
5859                 subquery = NEW.subquery,
5860                 negate = NEW.negate
5861     WHERE
5862         id = OLD.id;
5863
5864 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
5865     ON DELETE TO query.expr_xsubq
5866     DO INSTEAD
5867     DELETE FROM query.expression WHERE id = OLD.id;
5868
5869 CREATE TABLE action.fieldset (
5870     id              SERIAL          PRIMARY KEY,
5871     owner           INT             NOT NULL REFERENCES actor.usr (id)
5872                                     DEFERRABLE INITIALLY DEFERRED,
5873     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
5874                                     DEFERRABLE INITIALLY DEFERRED,
5875     status          TEXT            NOT NULL
5876                                     CONSTRAINT valid_status CHECK ( status in
5877                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
5878     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
5879     scheduled_time  TIMESTAMPTZ,
5880     applied_time    TIMESTAMPTZ,
5881     classname       TEXT            NOT NULL, -- an IDL class name
5882     name            TEXT            NOT NULL,
5883     stored_query    INT             REFERENCES query.stored_query (id)
5884                                     DEFERRABLE INITIALLY DEFERRED,
5885     pkey_value      TEXT,
5886     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
5887     CONSTRAINT fieldset_one_or_the_other CHECK (
5888         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
5889         (pkey_value IS NOT NULL AND stored_query IS NULL)
5890     )
5891     -- the CHECK constraint means we can update the fields for a single
5892     -- row without all the extra overhead involved in a query
5893 );
5894
5895 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
5896 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
5897
5898 CREATE TABLE action.fieldset_col_val (
5899     id              SERIAL  PRIMARY KEY,
5900     fieldset        INT     NOT NULL REFERENCES action.fieldset
5901                                          ON DELETE CASCADE
5902                                          DEFERRABLE INITIALLY DEFERRED,
5903     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
5904     val             TEXT,              -- value for the column ... NULL means, well, NULL
5905     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
5906 );
5907
5908 CREATE OR REPLACE FUNCTION action.apply_fieldset(
5909         fieldset_id IN INT,        -- id from action.fieldset
5910         table_name  IN TEXT,       -- table to be updated
5911         pkey_name   IN TEXT,       -- name of primary key column in that table
5912         query       IN TEXT        -- query constructed by qstore (for query-based
5913                                    --    fieldsets only; otherwise null
5914 )
5915 RETURNS TEXT AS $$
5916 DECLARE
5917         statement TEXT;
5918         fs_status TEXT;
5919         fs_pkey_value TEXT;
5920         fs_query TEXT;
5921         sep CHAR;
5922         status_code TEXT;
5923         msg TEXT;
5924         update_count INT;
5925         cv RECORD;
5926 BEGIN
5927         -- Sanity checks
5928         IF fieldset_id IS NULL THEN
5929                 RETURN 'Fieldset ID parameter is NULL';
5930         END IF;
5931         IF table_name IS NULL THEN
5932                 RETURN 'Table name parameter is NULL';
5933         END IF;
5934         IF pkey_name IS NULL THEN
5935                 RETURN 'Primary key name parameter is NULL';
5936         END IF;
5937         --
5938         statement := 'UPDATE ' || table_name || ' SET';
5939         --
5940         SELECT
5941                 status,
5942                 quote_literal( pkey_value )
5943         INTO
5944                 fs_status,
5945                 fs_pkey_value
5946         FROM
5947                 action.fieldset
5948         WHERE
5949                 id = fieldset_id;
5950         --
5951         IF fs_status IS NULL THEN
5952                 RETURN 'No fieldset found for id = ' || fieldset_id;
5953         ELSIF fs_status = 'APPLIED' THEN
5954                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
5955         END IF;
5956         --
5957         sep := '';
5958         FOR cv IN
5959                 SELECT  col,
5960                                 val
5961                 FROM    action.fieldset_col_val
5962                 WHERE   fieldset = fieldset_id
5963         LOOP
5964                 statement := statement || sep || ' ' || cv.col
5965                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
5966                 sep := ',';
5967         END LOOP;
5968         --
5969         IF sep = '' THEN
5970                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
5971         END IF;
5972         --
5973         -- Add the WHERE clause.  This differs according to whether it's a
5974         -- single-row fieldset or a query-based fieldset.
5975         --
5976         IF query IS NULL        AND fs_pkey_value IS NULL THEN
5977                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
5978         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
5979             fs_query := rtrim( query, ';' );
5980             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
5981                          || fs_query || ' );';
5982         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
5983                 statement := statement || ' WHERE ' || pkey_name || ' = '
5984                                      || fs_pkey_value || ';';
5985         ELSE  -- both are not null
5986                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
5987         END IF;
5988         --
5989         -- Execute the update
5990         --
5991         BEGIN
5992                 EXECUTE statement;
5993                 GET DIAGNOSTICS update_count = ROW_COUNT;
5994                 --
5995                 IF UPDATE_COUNT > 0 THEN
5996                         status_code := 'APPLIED';
5997                         msg := NULL;
5998                 ELSE
5999                         status_code := 'ERROR';
6000                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
6001         END IF;
6002         EXCEPTION WHEN OTHERS THEN
6003                 status_code := 'ERROR';
6004                 msg := 'Unable to apply fieldset ' || fieldset_id
6005                            || ': ' || sqlerrm;
6006         END;
6007         --
6008         -- Update fieldset status
6009         --
6010         UPDATE action.fieldset
6011         SET status       = status_code,
6012             applied_time = now()
6013         WHERE id = fieldset_id;
6014         --
6015         RETURN msg;
6016 END;
6017 $$ LANGUAGE plpgsql;
6018
6019 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
6020 /**
6021  * Applies a specified fieldset, using a supplied table name and primary
6022  * key name.  The query parameter should be non-null only for
6023  * query-based fieldsets.
6024  *
6025  * Returns NULL if successful, or an error message if not.
6026  */
6027 $$;
6028
6029 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
6030
6031 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
6032     SELECT  u.hold,
6033             c.circ_lib,
6034             count(*)
6035       FROM  action.unfulfilled_hold_list u
6036             JOIN asset.copy c ON (c.id = u.current_copy)
6037       GROUP BY 1,2;
6038
6039 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
6040     SELECT  hold,
6041             min(count)
6042       FROM  action.unfulfilled_hold_loops
6043       GROUP BY 1;
6044
6045 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
6046     SELECT  DISTINCT l.*
6047       FROM  action.unfulfilled_hold_loops l
6048             JOIN action.unfulfilled_hold_min_loop m USING (hold)
6049       WHERE l.count = m.min;
6050
6051 ALTER TABLE asset.copy
6052 ADD COLUMN dummy_isbn TEXT;
6053
6054 ALTER TABLE auditor.asset_copy_history
6055 ADD COLUMN dummy_isbn TEXT;
6056
6057 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
6058 -- Add corresponding new column to auditor.asset_copy_history
6059
6060 ALTER TABLE asset.copy
6061         ADD COLUMN status_changed_time TIMESTAMPTZ;
6062
6063 ALTER TABLE auditor.asset_copy_history
6064         ADD COLUMN status_changed_time TIMESTAMPTZ;
6065
6066 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
6067 RETURNS TRIGGER AS $$
6068 BEGIN
6069     IF NEW.status <> OLD.status THEN
6070         NEW.status_changed_time := now();
6071     END IF;
6072     RETURN NEW;
6073 END;
6074 $$ LANGUAGE plpgsql;
6075
6076 CREATE TRIGGER acp_status_changed_trig
6077         BEFORE UPDATE ON asset.copy
6078         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
6079
6080 ALTER TABLE asset.copy
6081 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6082
6083 ALTER TABLE auditor.asset_copy_history
6084 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6085
6086 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
6087 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
6088
6089 DROP INDEX IF EXISTS asset.copy_barcode_key;
6090 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
6091
6092 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
6093 -- AFTER INSERT OR UPDATE ON asset.copy
6094
6095 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
6096 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
6097
6098 -- Moke mostly parallel changes to action.circulation
6099 -- and action.aged_circulation
6100
6101 ALTER TABLE action.circulation
6102 ADD COLUMN workstation INT
6103     REFERENCES actor.workstation
6104         ON DELETE SET NULL
6105         DEFERRABLE INITIALLY DEFERRED;
6106
6107 ALTER TABLE action.aged_circulation
6108 ADD COLUMN workstation INT;
6109
6110 ALTER TABLE action.circulation
6111 ADD COLUMN parent_circ BIGINT
6112         REFERENCES action.circulation(id)
6113         DEFERRABLE INITIALLY DEFERRED;
6114
6115 CREATE UNIQUE INDEX circ_parent_idx
6116 ON action.circulation( parent_circ )
6117 WHERE parent_circ IS NOT NULL;
6118
6119 ALTER TABLE action.aged_circulation
6120 ADD COLUMN parent_circ BIGINT;
6121
6122 ALTER TABLE action.circulation
6123 ADD COLUMN checkin_workstation INT
6124         REFERENCES actor.workstation(id)
6125         ON DELETE SET NULL
6126         DEFERRABLE INITIALLY DEFERRED;
6127
6128 ALTER TABLE action.aged_circulation
6129 ADD COLUMN checkin_workstation INT;
6130
6131 ALTER TABLE action.circulation
6132 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6133
6134 ALTER TABLE action.aged_circulation
6135 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6136
6137 CREATE INDEX action_circulation_target_copy_idx
6138 ON action.circulation (target_copy);
6139
6140 CREATE INDEX action_aged_circulation_target_copy_idx
6141 ON action.aged_circulation (target_copy);
6142
6143 ALTER TABLE action.circulation
6144 DROP CONSTRAINT circulation_stop_fines_check;
6145
6146 ALTER TABLE action.circulation
6147         ADD CONSTRAINT circulation_stop_fines_check
6148         CHECK (stop_fines IN (
6149         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
6150
6151 -- Correct some long-standing misspellings involving variations of "recur"
6152
6153 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
6154 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6155
6156 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
6157 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6158
6159 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
6160 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
6161
6162 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
6163
6164 -- Might as well keep the comment in sync as well
6165 COMMENT ON TABLE config.rule_recurring_fine IS $$
6166 /*
6167  * Copyright (C) 2005  Georgia Public Library Service 
6168  * Mike Rylander <mrylander@gmail.com>
6169  *
6170  * Circulation Recurring Fine rules
6171  *
6172  * Each circulation is given a recurring fine amount based on one of
6173  * these rules.  The recurrence_interval should not be any shorter
6174  * than the interval between runs of the fine_processor.pl script
6175  * (which is run from CRON), or you could miss fines.
6176  * 
6177  *
6178  * ****
6179  *
6180  * This program is free software; you can redistribute it and/or
6181  * modify it under the terms of the GNU General Public License
6182  * as published by the Free Software Foundation; either version 2
6183  * of the License, or (at your option) any later version.
6184  *
6185  * This program is distributed in the hope that it will be useful,
6186  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6187  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6188  * GNU General Public License for more details.
6189  */
6190 $$;
6191
6192 -- Extend the name change to some related views:
6193
6194 DROP VIEW IF EXISTS reporter.overdue_circs;
6195
6196 CREATE OR REPLACE VIEW reporter.overdue_circs AS
6197 SELECT  *
6198   FROM  action.circulation
6199     WHERE checkin_time is null
6200                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
6201                                 AND due_date < now();
6202
6203 DROP VIEW IF EXISTS stats.fleshed_circulation;
6204
6205 DROP VIEW IF EXISTS stats.fleshed_copy;
6206
6207 CREATE VIEW stats.fleshed_copy AS
6208         SELECT  cp.*,
6209         CAST(cp.create_date AS DATE) AS create_date_day,
6210         CAST(cp.edit_date AS DATE) AS edit_date_day,
6211         DATE_TRUNC('hour', cp.create_date) AS create_date_hour,
6212         DATE_TRUNC('hour', cp.edit_date) AS edit_date_hour,
6213                 cn.label AS call_number_label,
6214                 cn.owning_lib,
6215                 rd.item_lang,
6216                 rd.item_type,
6217                 rd.item_form
6218         FROM    asset.copy cp
6219                 JOIN asset.call_number cn ON (cp.call_number = cn.id)
6220                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
6221
6222 CREATE VIEW stats.fleshed_circulation AS
6223         SELECT  c.*,
6224                 CAST(c.xact_start AS DATE) AS start_date_day,
6225                 CAST(c.xact_finish AS DATE) AS finish_date_day,
6226                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
6227                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
6228                 cp.call_number_label,
6229                 cp.owning_lib,
6230                 cp.item_lang,
6231                 cp.item_type,
6232                 cp.item_form
6233         FROM    action.circulation c
6234                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
6235
6236 -- Drop a view temporarily in order to alter action.all_circulation, upon
6237 -- which it is dependent.  We will recreate the view later.
6238
6239 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
6240
6241 -- You would think that CREATE OR REPLACE would be enough, but in testing
6242 -- PostgreSQL complained about renaming the columns in the view. So we
6243 -- drop the view first.
6244 DROP VIEW IF EXISTS action.all_circulation;
6245
6246 CREATE OR REPLACE VIEW action.all_circulation AS
6247     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6248         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6249         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6250         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6251         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6252         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6253       FROM  action.aged_circulation
6254             UNION ALL
6255     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,
6256         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,
6257         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6258         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6259         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6260         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6261         circ.parent_circ
6262       FROM  action.circulation circ
6263         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6264         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6265         JOIN actor.usr p ON (circ.usr = p.id)
6266         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6267         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
6268
6269 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
6270
6271 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
6272  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
6273    FROM asset."copy" cp
6274    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
6275    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
6276    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
6277   GROUP BY cp.id;
6278
6279 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
6280
6281 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
6282
6283 -- Rebuild dependent views
6284
6285 DROP VIEW IF EXISTS action.billable_circulations;
6286
6287 CREATE OR REPLACE VIEW action.billable_circulations AS
6288     SELECT  *
6289       FROM  action.circulation
6290       WHERE xact_finish IS NULL;
6291
6292 DROP VIEW IF EXISTS action.open_circulation;
6293
6294 CREATE OR REPLACE VIEW action.open_circulation AS
6295     SELECT  *
6296       FROM  action.circulation
6297       WHERE checkin_time IS NULL
6298       ORDER BY due_date;
6299
6300 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
6301 DECLARE
6302 found char := 'N';
6303 BEGIN
6304
6305     -- If there are any renewals for this circulation, don't archive or delete
6306     -- it yet.   We'll do so later, when we archive and delete the renewals.
6307
6308     SELECT 'Y' INTO found
6309     FROM action.circulation
6310     WHERE parent_circ = OLD.id
6311     LIMIT 1;
6312
6313     IF found = 'Y' THEN
6314         RETURN NULL;  -- don't delete
6315         END IF;
6316
6317     -- Archive a copy of the old row to action.aged_circulation
6318
6319     INSERT INTO action.aged_circulation
6320         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6321         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6322         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6323         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6324         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6325         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
6326       SELECT
6327         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6328         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6329         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6330         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6331         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6332         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6333         FROM action.all_circulation WHERE id = OLD.id;
6334
6335     RETURN OLD;
6336 END;
6337 $$ LANGUAGE 'plpgsql';
6338
6339 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
6340
6341 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
6342
6343 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
6344
6345 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$
6346 DECLARE
6347     current_requestor_group    permission.grp_tree%ROWTYPE;
6348     root_ou            actor.org_unit%ROWTYPE;
6349     requestor_object    actor.usr%ROWTYPE;
6350     user_object        actor.usr%ROWTYPE;
6351     item_object        asset.copy%ROWTYPE;
6352     item_cn_object        asset.call_number%ROWTYPE;
6353     rec_descriptor        metabib.rec_descriptor%ROWTYPE;
6354     current_mp_weight    FLOAT;
6355     matchpoint_weight    FLOAT;
6356     tmp_weight        FLOAT;
6357     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
6358     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
6359 BEGIN
6360     SELECT INTO root_ou * FROM actor.org_unit WHERE parent_ou IS NULL;
6361     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6362     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
6363     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6364     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6365     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
6366
6367     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
6368
6369     IF NOT FOUND THEN
6370         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
6371     ELSE
6372         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
6373     END IF;
6374
6375     LOOP 
6376         -- for each potential matchpoint for this ou and group ...
6377         FOR current_mp IN
6378             SELECT    m.*
6379               FROM    config.hold_matrix_matchpoint m
6380               WHERE    m.requestor_grp = current_requestor_group.id AND m.active
6381               ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
6382                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
6383                     CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
6384                     CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
6385                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
6386                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
6387
6388             current_mp_weight := 5.0;
6389
6390             IF current_mp.circ_modifier IS NOT NULL THEN
6391                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6392             END IF;
6393
6394             IF current_mp.marc_type IS NOT NULL THEN
6395                 IF item_object.circ_as_type IS NOT NULL THEN
6396                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6397                 ELSE
6398                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6399                 END IF;
6400             END IF;
6401
6402             IF current_mp.marc_form IS NOT NULL THEN
6403                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6404             END IF;
6405
6406             IF current_mp.marc_vr_format IS NOT NULL THEN
6407                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6408             END IF;
6409
6410             IF current_mp.juvenile_flag IS NOT NULL THEN
6411                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6412             END IF;
6413
6414             IF current_mp.ref_flag IS NOT NULL THEN
6415                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6416             END IF;
6417
6418
6419             -- caclulate the rule match weight
6420             IF current_mp.item_owning_ou IS NOT NULL AND current_mp.item_owning_ou <> root_ou.id THEN
6421                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
6422                 current_mp_weight := current_mp_weight - tmp_weight;
6423             END IF; 
6424
6425             IF current_mp.item_circ_ou IS NOT NULL AND current_mp.item_circ_ou <> root_ou.id THEN
6426                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
6427                 current_mp_weight := current_mp_weight - tmp_weight;
6428             END IF; 
6429
6430             IF current_mp.pickup_ou IS NOT NULL AND current_mp.pickup_ou <> root_ou.id THEN
6431                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
6432                 current_mp_weight := current_mp_weight - tmp_weight;
6433             END IF; 
6434
6435             IF current_mp.request_ou IS NOT NULL AND current_mp.request_ou <> root_ou.id THEN
6436                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
6437                 current_mp_weight := current_mp_weight - tmp_weight;
6438             END IF; 
6439
6440             IF current_mp.user_home_ou IS NOT NULL AND current_mp.user_home_ou <> root_ou.id THEN
6441                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
6442                 current_mp_weight := current_mp_weight - tmp_weight;
6443             END IF; 
6444
6445             -- set the matchpoint if we found the best one
6446             IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
6447                 matchpoint = current_mp;
6448                 matchpoint_weight = current_mp_weight;
6449             END IF;
6450
6451         END LOOP;
6452
6453         EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6454
6455         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
6456     END LOOP;
6457
6458     RETURN matchpoint.id;
6459 END;
6460 $func$ LANGUAGE plpgsql;
6461
6462 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$
6463 DECLARE
6464     matchpoint_id        INT;
6465     user_object        actor.usr%ROWTYPE;
6466     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
6467     standing_penalty    config.standing_penalty%ROWTYPE;
6468     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
6469     transit_source        actor.org_unit%ROWTYPE;
6470     item_object        asset.copy%ROWTYPE;
6471     ou_skip              actor.org_unit_setting%ROWTYPE;
6472     result            action.matrix_test_result;
6473     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
6474     hold_count        INT;
6475     hold_transit_prox    INT;
6476     frozen_hold_count    INT;
6477     context_org_list    INT[];
6478     done            BOOL := FALSE;
6479 BEGIN
6480     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6481     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
6482
6483     result.success := TRUE;
6484
6485     -- Fail if we couldn't find a user
6486     IF user_object.id IS NULL THEN
6487         result.fail_part := 'no_user';
6488         result.success := FALSE;
6489         done := TRUE;
6490         RETURN NEXT result;
6491         RETURN;
6492     END IF;
6493
6494     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6495
6496     -- Fail if we couldn't find a copy
6497     IF item_object.id IS NULL THEN
6498         result.fail_part := 'no_item';
6499         result.success := FALSE;
6500         done := TRUE;
6501         RETURN NEXT result;
6502         RETURN;
6503     END IF;
6504
6505     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
6506     result.matchpoint := matchpoint_id;
6507
6508     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
6509
6510     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
6511     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
6512         result.fail_part := 'circ.holds.target_skip_me';
6513         result.success := FALSE;
6514         done := TRUE;
6515         RETURN NEXT result;
6516         RETURN;
6517     END IF;
6518
6519     -- Fail if user is barred
6520     IF user_object.barred IS TRUE THEN
6521         result.fail_part := 'actor.usr.barred';
6522         result.success := FALSE;
6523         done := TRUE;
6524         RETURN NEXT result;
6525         RETURN;
6526     END IF;
6527
6528     -- Fail if we couldn't find any matchpoint (requires a default)
6529     IF matchpoint_id IS NULL THEN
6530         result.fail_part := 'no_matchpoint';
6531         result.success := FALSE;
6532         done := TRUE;
6533         RETURN NEXT result;
6534         RETURN;
6535     END IF;
6536
6537     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
6538
6539     IF hold_test.holdable IS FALSE THEN
6540         result.fail_part := 'config.hold_matrix_test.holdable';
6541         result.success := FALSE;
6542         done := TRUE;
6543         RETURN NEXT result;
6544     END IF;
6545
6546     IF hold_test.transit_range IS NOT NULL THEN
6547         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
6548         IF hold_test.distance_is_from_owner THEN
6549             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;
6550         ELSE
6551             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
6552         END IF;
6553
6554         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
6555
6556         IF NOT FOUND THEN
6557             result.fail_part := 'transit_range';
6558             result.success := FALSE;
6559             done := TRUE;
6560             RETURN NEXT result;
6561         END IF;
6562     END IF;
6563  
6564     FOR standing_penalty IN
6565         SELECT  DISTINCT csp.*
6566           FROM  actor.usr_standing_penalty usp
6567                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6568           WHERE usr = match_user
6569                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6570                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6571                 AND csp.block_list LIKE '%HOLD%' LOOP
6572
6573         result.fail_part := standing_penalty.name;
6574         result.success := FALSE;
6575         done := TRUE;
6576         RETURN NEXT result;
6577     END LOOP;
6578
6579     IF hold_test.stop_blocked_user IS TRUE THEN
6580         FOR standing_penalty IN
6581             SELECT  DISTINCT csp.*
6582               FROM  actor.usr_standing_penalty usp
6583                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6584               WHERE usr = match_user
6585                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6586                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6587                     AND csp.block_list LIKE '%CIRC%' LOOP
6588     
6589             result.fail_part := standing_penalty.name;
6590             result.success := FALSE;
6591             done := TRUE;
6592             RETURN NEXT result;
6593         END LOOP;
6594     END IF;
6595
6596     IF hold_test.max_holds IS NOT NULL THEN
6597         SELECT    INTO hold_count COUNT(*)
6598           FROM    action.hold_request
6599           WHERE    usr = match_user
6600             AND fulfillment_time IS NULL
6601             AND cancel_time IS NULL
6602             AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
6603
6604         IF hold_count >= hold_test.max_holds THEN
6605             result.fail_part := 'config.hold_matrix_test.max_holds';
6606             result.success := FALSE;
6607             done := TRUE;
6608             RETURN NEXT result;
6609         END IF;
6610     END IF;
6611
6612     IF item_object.age_protect IS NOT NULL THEN
6613         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
6614
6615         IF item_object.create_date + age_protect_object.age > NOW() THEN
6616             IF hold_test.distance_is_from_owner THEN
6617                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
6618             ELSE
6619                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
6620             END IF;
6621
6622             IF hold_transit_prox > age_protect_object.prox THEN
6623                 result.fail_part := 'config.rule_age_hold_protect.prox';
6624                 result.success := FALSE;
6625                 done := TRUE;
6626                 RETURN NEXT result;
6627             END IF;
6628         END IF;
6629     END IF;
6630
6631     IF NOT done THEN
6632         RETURN NEXT result;
6633     END IF;
6634
6635     RETURN;
6636 END;
6637 $func$ LANGUAGE plpgsql;
6638
6639 -- New post-delete trigger to propagate deletions to parent(s)
6640
6641 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
6642 BEGIN
6643
6644     -- Having deleted a renewal, we can delete the original circulation (or a previous
6645     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
6646     -- deletion of any prior parents, etc. recursively.
6647
6648     IF OLD.parent_circ IS NOT NULL THEN
6649         DELETE FROM action.circulation
6650         WHERE id = OLD.parent_circ;
6651     END IF;
6652
6653     RETURN OLD;
6654 END;
6655 $$ LANGUAGE 'plpgsql';
6656
6657 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
6658 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
6659
6660 -- This only gets inserted if there are no other id > 100 billing types
6661 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;
6662 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
6663
6664 -- Populate xact_type column in the materialized version of billable_xact_summary
6665
6666 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
6667 BEGIN
6668         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
6669                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
6670         RETURN NEW;
6671 END;
6672 $$ LANGUAGE PLPGSQL;
6673  
6674 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
6675 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
6676  
6677 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
6678 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
6679
6680 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
6681     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;
6682
6683 -- Generate the equivalent of compound subject entries from the existing rows
6684 -- so that we don't have to laboriously reindex them
6685
6686 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
6687 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
6688 --
6689 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
6690 --
6691 --INSERT INTO metabib.subject_field_entry (source, field, value)
6692 --    SELECT source, (
6693 --            SELECT id 
6694 --            FROM config.metabib_field
6695 --            WHERE field_class = 'subject' AND name = 'complete'
6696 --        ), 
6697 --        ARRAY_TO_STRING ( 
6698 --            ARRAY (
6699 --                SELECT value 
6700 --                FROM metabib.subject_field_entry msfe
6701 --                WHERE msfe.source = groupee.source
6702 --                ORDER BY source 
6703 --            ), ' ' 
6704 --        ) AS grouped
6705 --    FROM ( 
6706 --        SELECT source
6707 --        FROM metabib.subject_field_entry
6708 --        GROUP BY source
6709 --    ) AS groupee;
6710
6711 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
6712 DECLARE
6713         prev_billing    money.billing%ROWTYPE;
6714         old_billing     money.billing%ROWTYPE;
6715 BEGIN
6716         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
6717         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
6718
6719         IF OLD.id = old_billing.id THEN
6720                 UPDATE  money.materialized_billable_xact_summary
6721                   SET   last_billing_ts = prev_billing.billing_ts,
6722                         last_billing_note = prev_billing.note,
6723                         last_billing_type = prev_billing.billing_type
6724                   WHERE id = OLD.xact;
6725         END IF;
6726
6727         IF NOT OLD.voided THEN
6728                 UPDATE  money.materialized_billable_xact_summary
6729                   SET   total_owed = total_owed - OLD.amount,
6730                         balance_owed = balance_owed + OLD.amount
6731                   WHERE id = OLD.xact;
6732         END IF;
6733
6734         RETURN OLD;
6735 END;
6736 $$ LANGUAGE PLPGSQL;
6737
6738 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
6739 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
6740
6741 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
6742         use Unicode::Normalize;
6743         use Encode;
6744
6745         # When working with Unicode data, the first step is to decode it to
6746         # a byte string; after that, lowercasing is safe
6747         my $txt = lc(decode_utf8(shift));
6748         my $sf = shift;
6749
6750         $txt = NFD($txt);
6751         $txt =~ s/\pM+//go;     # Remove diacritics
6752
6753         $txt =~ s/\xE6/AE/go;   # Convert ae digraph
6754         $txt =~ s/\x{153}/OE/go;# Convert oe digraph
6755         $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
6756
6757         $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
6758         $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
6759
6760         $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;             # Convert Latin and Greek
6761         $txt =~ tr/\x{2113}\xF0\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LD /;     # Convert Misc
6762         $txt =~ tr/\'\[\]\|//d;                                                 # Remove Misc
6763
6764         if ($sf && $sf =~ /^a/o) {
6765                 my $commapos = index($txt,',');
6766                 if ($commapos > -1) {
6767                         if ($commapos != length($txt) - 1) {
6768                                 my @list = split /,/, $txt;
6769                                 my $first = shift @list;
6770                                 $txt = $first . ',' . join(' ', @list);
6771                         } else {
6772                                 $txt =~ s/,/ /go;
6773                         }
6774                 }
6775         } else {
6776                 $txt =~ s/,/ /go;
6777         }
6778
6779         $txt =~ s/\s+/ /go;     # Compress multiple spaces
6780         $txt =~ s/^\s+//o;      # Remove leading space
6781         $txt =~ s/\s+$//o;      # Remove trailing space
6782
6783         # Encoding the outgoing string is good practice, but not strictly
6784         # necessary in this case because we've stripped everything from it
6785         return encode_utf8($txt);
6786 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
6787
6788 -- Some handy functions, based on existing ones, to provide optional ingest normalization
6789
6790 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6791         SELECT SUBSTRING($1,$2);
6792 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6793
6794 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6795         SELECT SUBSTRING($1,1,$2);
6796 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6797
6798 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
6799         SELECT public.naco_normalize($1,'a');
6800 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6801
6802 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
6803         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
6804 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6805
6806 -- And ... a table in which to register them
6807
6808 CREATE TABLE config.index_normalizer (
6809         id              SERIAL  PRIMARY KEY,
6810         name            TEXT    UNIQUE NOT NULL,
6811         description     TEXT,
6812         func            TEXT    NOT NULL,
6813         param_count     INT     NOT NULL DEFAULT 0
6814 );
6815
6816 CREATE TABLE config.metabib_field_index_norm_map (
6817         id      SERIAL  PRIMARY KEY,
6818         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6819         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6820         params  TEXT,
6821         pos     INT     NOT NULL DEFAULT 0
6822 );
6823
6824 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6825         'NACO Normalize',
6826         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
6827         'naco_normalize',
6828         0
6829 );
6830
6831 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6832         'Normalize date range',
6833         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
6834         'split_date_range',
6835         1
6836 );
6837
6838 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6839         'NACO Normalize -- retain first comma',
6840         '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.',
6841         'naco_normalize_keep_comma',
6842         0
6843 );
6844
6845 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6846         'Strip Diacritics',
6847         'Convert text to NFD form and remove non-spacing combining marks.',
6848         'remove_diacritics',
6849         0
6850 );
6851
6852 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6853         'Up-case',
6854         'Convert text upper case.',
6855         'uppercase',
6856         0
6857 );
6858
6859 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6860         'Down-case',
6861         'Convert text lower case.',
6862         'lowercase',
6863         0
6864 );
6865
6866 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6867         'Extract Dewey-like number',
6868         'Extract a string of numeric characters ther resembles a DDC number.',
6869         'call_number_dewey',
6870         0
6871 );
6872
6873 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6874         'Left truncation',
6875         'Discard the specified number of characters from the left side of the string.',
6876         'left_trunc',
6877         1
6878 );
6879
6880 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6881         'Right truncation',
6882         'Include only the specified number of characters from the left side of the string.',
6883         'right_trunc',
6884         1
6885 );
6886
6887 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6888         'First word',
6889         'Include only the first space-separated word of a string.',
6890         'first_word',
6891         0
6892 );
6893
6894 INSERT INTO config.metabib_field_index_norm_map (field,norm)
6895         SELECT  m.id,
6896                 i.id
6897           FROM  config.metabib_field m,
6898                 config.index_normalizer i
6899           WHERE i.func IN ('naco_normalize','split_date_range');
6900
6901 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
6902 DECLARE
6903     normalizer      RECORD;
6904     value           TEXT := '';
6905 BEGIN
6906
6907     value := NEW.value;
6908
6909     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
6910         FOR normalizer IN
6911             SELECT  n.func AS func,
6912                     n.param_count AS param_count,
6913                     m.params AS params
6914               FROM  config.index_normalizer n
6915                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
6916               WHERE field = NEW.field AND m.pos < 0
6917               ORDER BY m.pos LOOP
6918                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
6919                     quote_literal( value ) ||
6920                     CASE
6921                         WHEN normalizer.param_count > 0
6922                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
6923                             ELSE ''
6924                         END ||
6925                     ')' INTO value;
6926
6927         END LOOP;
6928
6929         NEW.value := value;
6930     END IF;
6931
6932     IF NEW.index_vector = ''::tsvector THEN
6933         RETURN NEW;
6934     END IF;
6935
6936     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
6937         FOR normalizer IN
6938             SELECT  n.func AS func,
6939                     n.param_count AS param_count,
6940                     m.params AS params
6941               FROM  config.index_normalizer n
6942                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
6943               WHERE field = NEW.field AND m.pos >= 0
6944               ORDER BY m.pos LOOP
6945                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
6946                     quote_literal( value ) ||
6947                     CASE
6948                         WHEN normalizer.param_count > 0
6949                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
6950                             ELSE ''
6951                         END ||
6952                     ')' INTO value;
6953
6954         END LOOP;
6955     END IF;
6956
6957     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
6958         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
6959     ELSE
6960         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
6961     END IF;
6962
6963     RETURN NEW;
6964 END;
6965 $$ LANGUAGE PLPGSQL;
6966
6967 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
6968
6969 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
6970
6971 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
6972     SELECT  ARRAY_TO_STRING(
6973                 oils_xpath(
6974                     $1 ||
6975                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
6976                     $2,
6977                     $4
6978                 ),
6979                 $3
6980             );
6981 $func$ LANGUAGE SQL IMMUTABLE;
6982
6983 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
6984     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
6985 $func$ LANGUAGE SQL IMMUTABLE;
6986
6987 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
6988     SELECT oils_xpath_string( $1, $2, '', $3 );
6989 $func$ LANGUAGE SQL IMMUTABLE;
6990
6991 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
6992     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
6993 $func$ LANGUAGE SQL IMMUTABLE;
6994
6995 CREATE TYPE metabib.field_entry_template AS (
6996         field_class     TEXT,
6997         field           INT,
6998         source          BIGINT,
6999         value           TEXT
7000 );
7001
7002 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7003   use strict;
7004
7005   use XML::LibXSLT;
7006   use XML::LibXML;
7007
7008   my $doc = shift;
7009   my $xslt = shift;
7010
7011   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7012   # methods of parsing XML documents and stylesheets, in the hopes of broader
7013   # compatibility with distributions
7014   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7015
7016   # Cache the XML parser, if we do not already have one
7017   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7018     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7019
7020   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7021
7022   # Cache the XSLT processor, if we do not already have one
7023   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7024     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7025
7026   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7027     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7028
7029   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7030     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7031
7032   return $stylesheet->output_string(
7033     $stylesheet->transform(
7034       $parser->parse_string($doc)
7035     )
7036   );
7037
7038 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7039
7040 -- Add two columns so that the following function will compile.
7041 -- Eventually the label column will be NOT NULL, but not yet.
7042 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7043 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7044
7045 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7046 DECLARE
7047     bib     biblio.record_entry%ROWTYPE;
7048     idx     config.metabib_field%ROWTYPE;
7049     xfrm        config.xml_transform%ROWTYPE;
7050     prev_xfrm   TEXT;
7051     transformed_xml TEXT;
7052     xml_node    TEXT;
7053     xml_node_list   TEXT[];
7054     facet_text  TEXT;
7055     raw_text    TEXT;
7056     curr_text   TEXT;
7057     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7058     output_row  metabib.field_entry_template%ROWTYPE;
7059 BEGIN
7060
7061     -- Get the record
7062     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7063
7064     -- Loop over the indexing entries
7065     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7066
7067         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7068
7069         -- See if we can skip the XSLT ... it's expensive
7070         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7071             -- Can't skip the transform
7072             IF xfrm.xslt <> '---' THEN
7073                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7074             ELSE
7075                 transformed_xml := bib.marc;
7076             END IF;
7077
7078             prev_xfrm := xfrm.name;
7079         END IF;
7080
7081         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7082
7083         raw_text := NULL;
7084         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7085             CONTINUE WHEN xml_node !~ E'^\\s*<';
7086
7087             curr_text := ARRAY_TO_STRING(
7088                 oils_xpath( '//text()',
7089                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7090                         REGEXP_REPLACE( -- This escapes embeded <s
7091                             xml_node,
7092                             $re$(>[^<]+)(<)([^>]+<)$re$,
7093                             E'\\1&lt;\\3',
7094                             'g'
7095                         ),
7096                         '&(?!amp;)',
7097                         '&amp;',
7098                         'g'
7099                     )
7100                 ),
7101                 ' '
7102             );
7103
7104             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7105
7106             IF raw_text IS NOT NULL THEN
7107                 raw_text := raw_text || joiner;
7108             END IF;
7109
7110             raw_text := COALESCE(raw_text,'') || curr_text;
7111
7112             -- insert raw node text for faceting
7113             IF idx.facet_field THEN
7114
7115                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7116                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7117                 ELSE
7118                     facet_text := curr_text;
7119                 END IF;
7120
7121                 output_row.field_class = idx.field_class;
7122                 output_row.field = -1 * idx.id;
7123                 output_row.source = rid;
7124                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7125
7126                 RETURN NEXT output_row;
7127             END IF;
7128
7129         END LOOP;
7130
7131         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7132
7133         -- insert combined node text for searching
7134         IF idx.search_field THEN
7135             output_row.field_class = idx.field_class;
7136             output_row.field = idx.id;
7137             output_row.source = rid;
7138             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7139
7140             RETURN NEXT output_row;
7141         END IF;
7142
7143     END LOOP;
7144
7145 END;
7146 $func$ LANGUAGE PLPGSQL;
7147
7148 -- default to a space joiner
7149 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7150         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7151 $func$ LANGUAGE SQL;
7152
7153 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7154
7155 use MARC::Record;
7156 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7157
7158 my $xml = shift;
7159 my $r = MARC::Record->new_from_xml( $xml );
7160
7161 return_next( { tag => 'LDR', value => $r->leader } );
7162
7163 for my $f ( $r->fields ) {
7164     if ($f->is_control_field) {
7165         return_next({ tag => $f->tag, value => $f->data });
7166     } else {
7167         for my $s ($f->subfields) {
7168             return_next({
7169                 tag      => $f->tag,
7170                 ind1     => $f->indicator(1),
7171                 ind2     => $f->indicator(2),
7172                 subfield => $s->[0],
7173                 value    => $s->[1]
7174             });
7175
7176             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7177                 my $trim = $f->indicator(2) || 0;
7178                 return_next({
7179                     tag      => 'tnf',
7180                     ind1     => $f->indicator(1),
7181                     ind2     => $f->indicator(2),
7182                     subfield => 'a',
7183                     value    => substr( $s->[1], $trim )
7184                 });
7185             }
7186         }
7187     }
7188 }
7189
7190 return undef;
7191
7192 $func$ LANGUAGE PLPERLU;
7193
7194 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7195 DECLARE
7196     bib biblio.record_entry%ROWTYPE;
7197     output  metabib.full_rec%ROWTYPE;
7198     field   RECORD;
7199 BEGIN
7200     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7201
7202     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7203         output.record := rid;
7204         output.ind1 := field.ind1;
7205         output.ind2 := field.ind2;
7206         output.tag := field.tag;
7207         output.subfield := field.subfield;
7208         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7209             output.value := naco_normalize(field.value, field.subfield);
7210         ELSE
7211             output.value := field.value;
7212         END IF;
7213
7214         CONTINUE WHEN output.value IS NULL;
7215
7216         RETURN NEXT output;
7217     END LOOP;
7218 END;
7219 $func$ LANGUAGE PLPGSQL;
7220
7221 -- functions to create auditor objects
7222
7223 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7224 BEGIN
7225     EXECUTE $$
7226         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7227     $$;
7228         RETURN TRUE;
7229 END;
7230 $creator$ LANGUAGE 'plpgsql';
7231
7232 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7233 BEGIN
7234     EXECUTE $$
7235         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7236             audit_id    BIGINT                          PRIMARY KEY,
7237             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7238             audit_action        TEXT                            NOT NULL,
7239             LIKE $$ || sch || $$.$$ || tbl || $$
7240         );
7241     $$;
7242         RETURN TRUE;
7243 END;
7244 $creator$ LANGUAGE 'plpgsql';
7245
7246 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7247 BEGIN
7248     EXECUTE $$
7249         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7250         RETURNS TRIGGER AS $func$
7251         BEGIN
7252             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7253                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7254                     now(),
7255                     SUBSTR(TG_OP,1,1),
7256                     OLD.*;
7257             RETURN NULL;
7258         END;
7259         $func$ LANGUAGE 'plpgsql';
7260     $$;
7261         RETURN TRUE;
7262 END;
7263 $creator$ LANGUAGE 'plpgsql';
7264
7265 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7266 BEGIN
7267     EXECUTE $$
7268         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7269             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7270             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7271     $$;
7272         RETURN TRUE;
7273 END;
7274 $creator$ LANGUAGE 'plpgsql';
7275
7276 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7277 BEGIN
7278     EXECUTE $$
7279         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7280             SELECT      -1, now() as audit_time, '-' as audit_action, *
7281               FROM      $$ || sch || $$.$$ || tbl || $$
7282                 UNION ALL
7283             SELECT      *
7284               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7285     $$;
7286         RETURN TRUE;
7287 END;
7288 $creator$ LANGUAGE 'plpgsql';
7289
7290 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7291
7292 -- The main event
7293
7294 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7295 BEGIN
7296     PERFORM auditor.create_auditor_seq(sch, tbl);
7297     PERFORM auditor.create_auditor_history(sch, tbl);
7298     PERFORM auditor.create_auditor_func(sch, tbl);
7299     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7300     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7301     RETURN TRUE;
7302 END;
7303 $creator$ LANGUAGE 'plpgsql';
7304
7305 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7306
7307 ALTER TABLE action.hold_request
7308 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7309
7310 ALTER TABLE action.hold_request
7311 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7312
7313 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7314
7315 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7316
7317 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7318
7319 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7320
7321 -- Add claims_never_checked_out_count to actor.usr, related history
7322
7323 ALTER TABLE actor.usr ADD COLUMN
7324         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7325
7326 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7327         claims_never_checked_out_count INT;
7328
7329 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7330
7331 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7332
7333 -----------
7334
7335 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7336 BEGIN
7337         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7338                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7339                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7340                 END IF;
7341                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7342                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7343                 END IF;
7344                 IF NEW.stop_fines = 'LOST' THEN
7345                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7346                 END IF;
7347         END IF;
7348         RETURN NEW;
7349 END;
7350 $$ LANGUAGE 'plpgsql';
7351
7352 -- Create new table acq.fund_allocation_percent
7353 -- Populate it from acq.fund_allocation
7354 -- Convert all percentages to amounts in acq.fund_allocation
7355
7356 CREATE TABLE acq.fund_allocation_percent
7357 (
7358     id                   SERIAL            PRIMARY KEY,
7359     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7360                                                DEFERRABLE INITIALLY DEFERRED,
7361     org                  INT               NOT NULL REFERENCES actor.org_unit
7362                                                DEFERRABLE INITIALLY DEFERRED,
7363     fund_code            TEXT,
7364     percent              NUMERIC           NOT NULL,
7365     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7366                                                DEFERRABLE INITIALLY DEFERRED,
7367     note                 TEXT,
7368     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7369     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7370     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7371 );
7372
7373 -- Trigger function to validate combination of org_unit and fund_code
7374
7375 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7376 RETURNS TRIGGER AS $$
7377 --
7378 DECLARE
7379 --
7380 dummy int := 0;
7381 --
7382 BEGIN
7383     SELECT
7384         1
7385     INTO
7386         dummy
7387     FROM
7388         acq.fund
7389     WHERE
7390         org = NEW.org
7391         AND code = NEW.fund_code
7392         LIMIT 1;
7393     --
7394     IF dummy = 1 then
7395         RETURN NEW;
7396     ELSE
7397         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7398     END IF;
7399 END;
7400 $$ LANGUAGE plpgsql;
7401
7402 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7403     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7404     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7405
7406 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7407 RETURNS TRIGGER AS $$
7408 DECLARE
7409 --
7410 total_percent numeric;
7411 --
7412 BEGIN
7413     SELECT
7414         sum( percent )
7415     INTO
7416         total_percent
7417     FROM
7418         acq.fund_allocation_percent AS fap
7419     WHERE
7420         fap.funding_source = NEW.funding_source;
7421     --
7422     IF total_percent > 100 THEN
7423         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7424             NEW.funding_source;
7425     ELSE
7426         RETURN NEW;
7427     END IF;
7428 END;
7429 $$ LANGUAGE plpgsql;
7430
7431 CREATE TRIGGER acqfap_limit_100_trig
7432     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7433     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7434
7435 -- Populate new table from acq.fund_allocation
7436
7437 INSERT INTO acq.fund_allocation_percent
7438 (
7439     funding_source,
7440     org,
7441     fund_code,
7442     percent,
7443     allocator,
7444     note,
7445     create_time
7446 )
7447     SELECT
7448         fa.funding_source,
7449         fund.org,
7450         fund.code,
7451         fa.percent,
7452         fa.allocator,
7453         fa.note,
7454         fa.create_time
7455     FROM
7456         acq.fund_allocation AS fa
7457             INNER JOIN acq.fund AS fund
7458                 ON ( fa.fund = fund.id )
7459     WHERE
7460         fa.percent is not null
7461     ORDER BY
7462         fund.org;
7463
7464 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7465
7466 -- Algorithm to apply to each funding source:
7467
7468 -- 1. Add up the credits.
7469 -- 2. Add up the percentages.
7470 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7471 --    fractional cents from the result.  This is the total amount to be allocated.
7472 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7473 --    fractional cents to get a preliminary amount.
7474 -- 5. Add up the preliminary amounts for all the allocations.
7475 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7476 --    number of residual cents (resulting from having dropped fractional cents) that
7477 --    must be distributed across the funds in order to make the total of the amounts
7478 --    match the total allocation.
7479 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7480 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7481 --    for each successive fund, until all the residual cents have been exhausted.
7482
7483 -- Result: the sum of the individual allocations now equals the total to be allocated,
7484 -- to the penny.  The individual amounts match the percentages as closely as possible,
7485 -- given the constraint that the total must match.
7486
7487 CREATE OR REPLACE FUNCTION acq.apply_percents()
7488 RETURNS VOID AS $$
7489 declare
7490 --
7491 tot              RECORD;
7492 fund             RECORD;
7493 tot_cents        INTEGER;
7494 src              INTEGER;
7495 id               INTEGER[];
7496 curr_id          INTEGER;
7497 pennies          NUMERIC[];
7498 curr_amount      NUMERIC;
7499 i                INTEGER;
7500 total_of_floors  INTEGER;
7501 total_percent    NUMERIC;
7502 total_allocation INTEGER;
7503 residue          INTEGER;
7504 --
7505 begin
7506         RAISE NOTICE 'Applying percents';
7507         FOR tot IN
7508                 SELECT
7509                         fsrc.funding_source,
7510                         sum( fsrc.amount ) AS total
7511                 FROM
7512                         acq.funding_source_credit AS fsrc
7513                 WHERE fsrc.funding_source IN
7514                         ( SELECT DISTINCT fa.funding_source
7515                           FROM acq.fund_allocation AS fa
7516                           WHERE fa.percent IS NOT NULL )
7517                 GROUP BY
7518                         fsrc.funding_source
7519         LOOP
7520                 tot_cents = floor( tot.total * 100 );
7521                 src = tot.funding_source;
7522                 RAISE NOTICE 'Funding source % total %',
7523                         src, tot_cents;
7524                 i := 0;
7525                 total_of_floors := 0;
7526                 total_percent := 0;
7527                 --
7528                 FOR fund in
7529                         SELECT
7530                                 fa.id,
7531                                 fa.percent,
7532                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7533                         FROM
7534                                 acq.fund_allocation AS fa
7535                         WHERE
7536                                 fa.funding_source = src
7537                                 AND fa.percent IS NOT NULL
7538                         ORDER BY
7539                                 mod( fa.percent * tot_cents / 100, 1 ),
7540                                 fa.fund,
7541                                 fa.id
7542                 LOOP
7543                         RAISE NOTICE '   %: %',
7544                                 fund.id,
7545                                 fund.floor_pennies;
7546                         i := i + 1;
7547                         id[i] = fund.id;
7548                         pennies[i] = fund.floor_pennies;
7549                         total_percent := total_percent + fund.percent;
7550                         total_of_floors := total_of_floors + pennies[i];
7551                 END LOOP;
7552                 total_allocation := floor( total_percent * tot_cents /100 );
7553                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7554                 residue := total_allocation - total_of_floors;
7555                 RAISE NOTICE 'Residue: %', residue;
7556                 --
7557                 -- Post the calculated amounts, revising as needed to
7558                 -- distribute the rounding error
7559                 --
7560                 WHILE i > 0 LOOP
7561                         IF residue > 0 THEN
7562                                 pennies[i] = pennies[i] + 1;
7563                                 residue := residue - 1;
7564                         END IF;
7565                         --
7566                         -- Post amount
7567                         --
7568                         curr_id     := id[i];
7569                         curr_amount := trunc( pennies[i] / 100, 2 );
7570                         --
7571                         UPDATE
7572                                 acq.fund_allocation AS fa
7573                         SET
7574                                 amount = curr_amount,
7575                                 percent = NULL
7576                         WHERE
7577                                 fa.id = curr_id;
7578                         --
7579                         RAISE NOTICE '   ID % and amount %',
7580                                 curr_id,
7581                                 curr_amount;
7582                         i = i - 1;
7583                 END LOOP;
7584         END LOOP;
7585 end;
7586 $$ LANGUAGE 'plpgsql';
7587
7588 -- Run the temporary function
7589
7590 select * from acq.apply_percents();
7591
7592 -- Drop the temporary function now that we're done with it
7593
7594 DROP FUNCTION IF EXISTS acq.apply_percents();
7595
7596 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7597
7598 -- If the following step fails, it's probably because there are still some non-null percent values in
7599 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7600 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7601 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7602 -- slipped in afterwards.
7603
7604 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7605 -- procedure acq.apply_percents() as defined above.
7606
7607 ALTER TABLE acq.fund_allocation
7608 ALTER COLUMN amount SET NOT NULL;
7609
7610 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7611     SELECT  fund,
7612             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7613     FROM acq.fund_allocation a
7614          JOIN acq.fund f ON (a.fund = f.id)
7615          JOIN acq.funding_source s ON (a.funding_source = s.id)
7616     GROUP BY 1;
7617
7618 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7619     SELECT  funding_source,
7620             SUM(a.amount)::NUMERIC(100,2) AS amount
7621     FROM  acq.fund_allocation a
7622     GROUP BY 1;
7623
7624 ALTER TABLE acq.fund_allocation
7625 DROP COLUMN percent;
7626
7627 CREATE TABLE asset.copy_location_order
7628 (
7629         id              SERIAL           PRIMARY KEY,
7630         location        INT              NOT NULL
7631                                              REFERENCES asset.copy_location
7632                                              ON DELETE CASCADE
7633                                              DEFERRABLE INITIALLY DEFERRED,
7634         org             INT              NOT NULL
7635                                              REFERENCES actor.org_unit
7636                                              ON DELETE CASCADE
7637                                              DEFERRABLE INITIALLY DEFERRED,
7638         position        INT              NOT NULL DEFAULT 0,
7639         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7640 );
7641
7642 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7643
7644 -- If you ran this before its most recent incarnation:
7645 -- delete from config.upgrade_log where version = '0328';
7646 -- alter table money.credit_card_payment drop column cc_name;
7647
7648 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7649 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7650
7651 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$
7652 DECLARE
7653     current_group    permission.grp_tree%ROWTYPE;
7654     user_object    actor.usr%ROWTYPE;
7655     item_object    asset.copy%ROWTYPE;
7656     cn_object    asset.call_number%ROWTYPE;
7657     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7658     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7659     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7660 BEGIN
7661     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7662     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7663     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7664     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7665     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7666
7667     LOOP 
7668         -- for each potential matchpoint for this ou and group ...
7669         FOR current_mp IN
7670             SELECT  m.*
7671               FROM  config.circ_matrix_matchpoint m
7672                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
7673                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
7674               WHERE m.grp = current_group.id
7675                     AND m.active
7676                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
7677                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
7678               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
7679                     CASE WHEN m.copy_owning_lib IS NOT NULL
7680                         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 )
7681                         ELSE 0
7682                     END +
7683                     CASE WHEN m.copy_circ_lib IS NOT NULL
7684                         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 )
7685                         ELSE 0
7686                     END +
7687                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
7688                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
7689                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
7690                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
7691                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
7692                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
7693                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
7694                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
7695                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
7696
7697             IF current_mp.is_renewal IS NOT NULL THEN
7698                 CONTINUE WHEN current_mp.is_renewal <> renewal;
7699             END IF;
7700
7701             IF current_mp.circ_modifier IS NOT NULL THEN
7702                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
7703             END IF;
7704
7705             IF current_mp.marc_type IS NOT NULL THEN
7706                 IF item_object.circ_as_type IS NOT NULL THEN
7707                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
7708                 ELSE
7709                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
7710                 END IF;
7711             END IF;
7712
7713             IF current_mp.marc_form IS NOT NULL THEN
7714                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
7715             END IF;
7716
7717             IF current_mp.marc_vr_format IS NOT NULL THEN
7718                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
7719             END IF;
7720
7721             IF current_mp.ref_flag IS NOT NULL THEN
7722                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
7723             END IF;
7724
7725             IF current_mp.juvenile_flag IS NOT NULL THEN
7726                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
7727             END IF;
7728
7729             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
7730                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
7731             END IF;
7732
7733             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
7734                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
7735             END IF;
7736
7737
7738             -- everything was undefined or matched
7739             matchpoint = current_mp;
7740
7741             EXIT WHEN matchpoint.id IS NOT NULL;
7742         END LOOP;
7743
7744         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
7745
7746         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
7747     END LOOP;
7748
7749     RETURN matchpoint;
7750 END;
7751 $func$ LANGUAGE plpgsql;
7752
7753 CREATE TYPE action.hold_stats AS (
7754     hold_count              INT,
7755     copy_count              INT,
7756     available_count         INT,
7757     total_copy_ratio        FLOAT,
7758     available_copy_ratio    FLOAT
7759 );
7760
7761 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
7762 DECLARE
7763     output          action.hold_stats%ROWTYPE;
7764     hold_count      INT := 0;
7765     copy_count      INT := 0;
7766     available_count INT := 0;
7767     hold_map_data   RECORD;
7768 BEGIN
7769
7770     output.hold_count := 0;
7771     output.copy_count := 0;
7772     output.available_count := 0;
7773
7774     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
7775       FROM  action.hold_copy_map m
7776             JOIN action.hold_request h ON (m.hold = h.id)
7777       WHERE m.target_copy = copy_id
7778             AND NOT h.frozen;
7779
7780     output.hold_count := hold_count;
7781
7782     IF output.hold_count > 0 THEN
7783         FOR hold_map_data IN
7784             SELECT  DISTINCT m.target_copy,
7785                     acp.status
7786               FROM  action.hold_copy_map m
7787                     JOIN asset.copy acp ON (m.target_copy = acp.id)
7788                     JOIN action.hold_request h ON (m.hold = h.id)
7789               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
7790         LOOP
7791             output.copy_count := output.copy_count + 1;
7792             IF hold_map_data.status IN (0,7,12) THEN
7793                 output.available_count := output.available_count + 1;
7794             END IF;
7795         END LOOP;
7796         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
7797         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
7798
7799     END IF;
7800
7801     RETURN output;
7802
7803 END;
7804 $func$ LANGUAGE PLPGSQL;
7805
7806 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
7807 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
7808
7809 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
7810
7811 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7812 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7813
7814 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
7815     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
7816     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
7817     copy_owning_lib
7818 );
7819
7820 -- Return the correct fail_part when the item can't be found
7821 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$
7822 DECLARE
7823     user_object        actor.usr%ROWTYPE;
7824     standing_penalty    config.standing_penalty%ROWTYPE;
7825     item_object        asset.copy%ROWTYPE;
7826     item_status_object    config.copy_status%ROWTYPE;
7827     item_location_object    asset.copy_location%ROWTYPE;
7828     result            action.matrix_test_result;
7829     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
7830     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
7831     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
7832     hold_ratio          action.hold_stats%ROWTYPE;
7833     penalty_type         TEXT;
7834     tmp_grp         INT;
7835     items_out        INT;
7836     context_org_list        INT[];
7837     done            BOOL := FALSE;
7838 BEGIN
7839     result.success := TRUE;
7840
7841     -- Fail if the user is BARRED
7842     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7843
7844     -- Fail if we couldn't find the user 
7845     IF user_object.id IS NULL THEN
7846         result.fail_part := 'no_user';
7847         result.success := FALSE;
7848         done := TRUE;
7849         RETURN NEXT result;
7850         RETURN;
7851     END IF;
7852
7853     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7854
7855     -- Fail if we couldn't find the item 
7856     IF item_object.id IS NULL THEN
7857         result.fail_part := 'no_item';
7858         result.success := FALSE;
7859         done := TRUE;
7860         RETURN NEXT result;
7861         RETURN;
7862     END IF;
7863
7864     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
7865     result.matchpoint := circ_test.id;
7866
7867     -- Fail if we couldn't find a matchpoint
7868     IF result.matchpoint IS NULL THEN
7869         result.fail_part := 'no_matchpoint';
7870         result.success := FALSE;
7871         done := TRUE;
7872         RETURN NEXT result;
7873     END IF;
7874
7875     IF user_object.barred IS TRUE THEN
7876         result.fail_part := 'actor.usr.barred';
7877         result.success := FALSE;
7878         done := TRUE;
7879         RETURN NEXT result;
7880     END IF;
7881
7882     -- Fail if the item can't circulate
7883     IF item_object.circulate IS FALSE THEN
7884         result.fail_part := 'asset.copy.circulate';
7885         result.success := FALSE;
7886         done := TRUE;
7887         RETURN NEXT result;
7888     END IF;
7889
7890     -- Fail if the item isn't in a circulateable status on a non-renewal
7891     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
7892         result.fail_part := 'asset.copy.status';
7893         result.success := FALSE;
7894         done := TRUE;
7895         RETURN NEXT result;
7896     ELSIF renewal AND item_object.status <> 1 THEN
7897         result.fail_part := 'asset.copy.status';
7898         result.success := FALSE;
7899         done := TRUE;
7900         RETURN NEXT result;
7901     END IF;
7902
7903     -- Fail if the item can't circulate because of the shelving location
7904     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
7905     IF item_location_object.circulate IS FALSE THEN
7906         result.fail_part := 'asset.copy_location.circulate';
7907         result.success := FALSE;
7908         done := TRUE;
7909         RETURN NEXT result;
7910     END IF;
7911
7912     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
7913
7914     -- Fail if the test is set to hard non-circulating
7915     IF circ_test.circulate IS FALSE THEN
7916         result.fail_part := 'config.circ_matrix_test.circulate';
7917         result.success := FALSE;
7918         done := TRUE;
7919         RETURN NEXT result;
7920     END IF;
7921
7922     -- Fail if the total copy-hold ratio is too low
7923     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
7924         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
7925         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
7926             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
7927             result.success := FALSE;
7928             done := TRUE;
7929             RETURN NEXT result;
7930         END IF;
7931     END IF;
7932
7933     -- Fail if the available copy-hold ratio is too low
7934     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
7935         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
7936         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
7937             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
7938             result.success := FALSE;
7939             done := TRUE;
7940             RETURN NEXT result;
7941         END IF;
7942     END IF;
7943
7944     IF renewal THEN
7945         penalty_type = '%RENEW%';
7946     ELSE
7947         penalty_type = '%CIRC%';
7948     END IF;
7949
7950     FOR standing_penalty IN
7951         SELECT  DISTINCT csp.*
7952           FROM  actor.usr_standing_penalty usp
7953                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
7954           WHERE usr = match_user
7955                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
7956                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
7957                 AND csp.block_list LIKE penalty_type LOOP
7958
7959         result.fail_part := standing_penalty.name;
7960         result.success := FALSE;
7961         done := TRUE;
7962         RETURN NEXT result;
7963     END LOOP;
7964
7965     -- Fail if the user has too many items with specific circ_modifiers checked out
7966     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
7967         SELECT  INTO items_out COUNT(*)
7968           FROM  action.circulation circ
7969             JOIN asset.copy cp ON (cp.id = circ.target_copy)
7970           WHERE circ.usr = match_user
7971                AND circ.circ_lib IN ( SELECT * FROM explode_array(context_org_list) )
7972             AND circ.checkin_time IS NULL
7973             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
7974             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);
7975         IF items_out >= out_by_circ_mod.items_out THEN
7976             result.fail_part := 'config.circ_matrix_circ_mod_test';
7977             result.success := FALSE;
7978             done := TRUE;
7979             RETURN NEXT result;
7980         END IF;
7981     END LOOP;
7982
7983     -- If we passed everything, return the successful matchpoint id
7984     IF NOT done THEN
7985         RETURN NEXT result;
7986     END IF;
7987
7988     RETURN;
7989 END;
7990 $func$ LANGUAGE plpgsql;
7991
7992 CREATE TABLE config.remote_account (
7993     id          SERIAL  PRIMARY KEY,
7994     label       TEXT    NOT NULL,
7995     host        TEXT    NOT NULL,   -- name or IP, :port optional
7996     username    TEXT,               -- optional, since we could default to $USER
7997     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
7998     account     TEXT,               -- aka profile or FTP "account" command
7999     path        TEXT,               -- aka directory
8000     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8001     last_activity TIMESTAMP WITH TIME ZONE
8002 );
8003
8004 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8005     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8006     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8007         vendcode    TEXT,
8008         vendacct    TEXT
8009
8010 ) INHERITS (config.remote_account);
8011
8012 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8013
8014 CREATE TABLE acq.claim_type (
8015         id             SERIAL           PRIMARY KEY,
8016         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8017                                                  DEFERRABLE INITIALLY DEFERRED,
8018         code           TEXT             NOT NULL,
8019         description    TEXT             NOT NULL,
8020         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8021 );
8022
8023 CREATE TABLE acq.claim (
8024         id             SERIAL           PRIMARY KEY,
8025         type           INT              NOT NULL REFERENCES acq.claim_type
8026                                                  DEFERRABLE INITIALLY DEFERRED,
8027         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8028                                                  DEFERRABLE INITIALLY DEFERRED
8029 );
8030
8031 CREATE TABLE acq.claim_policy (
8032         id              SERIAL       PRIMARY KEY,
8033         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8034                                      DEFERRABLE INITIALLY DEFERRED,
8035         name            TEXT         NOT NULL,
8036         description     TEXT         NOT NULL,
8037         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8038 );
8039
8040 -- Add a san column for EDI. 
8041 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8042
8043 ALTER TABLE acq.provider ADD COLUMN san INT;
8044
8045 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8046
8047 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8048 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8049
8050 ALTER TABLE acq.provider
8051         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8052
8053 ALTER TABLE acq.provider
8054         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8055
8056 ALTER TABLE acq.provider
8057         ADD COLUMN url TEXT;
8058
8059 ALTER TABLE acq.provider
8060         ADD COLUMN email TEXT;
8061
8062 ALTER TABLE acq.provider
8063         ADD COLUMN phone TEXT;
8064
8065 ALTER TABLE acq.provider
8066         ADD COLUMN fax_phone TEXT;
8067
8068 ALTER TABLE acq.provider
8069         ADD COLUMN default_claim_policy INT
8070                 REFERENCES acq.claim_policy
8071                 DEFERRABLE INITIALLY DEFERRED;
8072
8073 ALTER TABLE action.transit_copy
8074 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8075                                                          DEFERRABLE INITIALLY DEFERRED;
8076
8077 DROP SCHEMA IF EXISTS booking CASCADE;
8078
8079 CREATE SCHEMA booking;
8080
8081 CREATE TABLE booking.resource_type (
8082         id             SERIAL          PRIMARY KEY,
8083         name           TEXT            NOT NULL,
8084         fine_interval  INTERVAL,
8085         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
8086         owner          INT             NOT NULL
8087                                        REFERENCES actor.org_unit( id )
8088                                        DEFERRABLE INITIALLY DEFERRED,
8089         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
8090         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
8091     record         BIGINT          REFERENCES biblio.record_entry (id)
8092                                        DEFERRABLE INITIALLY DEFERRED,
8093     max_fine       NUMERIC(8,2),
8094     elbow_room     INTERVAL,
8095     CONSTRAINT brt_name_or_record_once_per_owner UNIQUE(owner, name, record)
8096 );
8097
8098 CREATE TABLE booking.resource (
8099         id             SERIAL           PRIMARY KEY,
8100         owner          INT              NOT NULL
8101                                         REFERENCES actor.org_unit(id)
8102                                         DEFERRABLE INITIALLY DEFERRED,
8103         type           INT              NOT NULL
8104                                         REFERENCES booking.resource_type(id)
8105                                         DEFERRABLE INITIALLY DEFERRED,
8106         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
8107         barcode        TEXT             NOT NULL,
8108         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
8109         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8110         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8111         CONSTRAINT br_unique UNIQUE (owner, barcode)
8112 );
8113
8114 -- For non-catalog items: hijack barcode for name/description
8115
8116 CREATE TABLE booking.resource_attr (
8117         id              SERIAL          PRIMARY KEY,
8118         owner           INT             NOT NULL
8119                                         REFERENCES actor.org_unit(id)
8120                                         DEFERRABLE INITIALLY DEFERRED,
8121         name            TEXT            NOT NULL,
8122         resource_type   INT             NOT NULL
8123                                         REFERENCES booking.resource_type(id)
8124                                         ON DELETE CASCADE
8125                                         DEFERRABLE INITIALLY DEFERRED,
8126         required        BOOLEAN         NOT NULL DEFAULT FALSE,
8127         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
8128 );
8129
8130 CREATE TABLE booking.resource_attr_value (
8131         id               SERIAL         PRIMARY KEY,
8132         owner            INT            NOT NULL
8133                                         REFERENCES actor.org_unit(id)
8134                                         DEFERRABLE INITIALLY DEFERRED,
8135         attr             INT            NOT NULL
8136                                         REFERENCES booking.resource_attr(id)
8137                                         DEFERRABLE INITIALLY DEFERRED,
8138         valid_value      TEXT           NOT NULL,
8139         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
8140 );
8141
8142 CREATE TABLE booking.resource_attr_map (
8143         id               SERIAL         PRIMARY KEY,
8144         resource         INT            NOT NULL
8145                                         REFERENCES booking.resource(id)
8146                                         ON DELETE CASCADE
8147                                         DEFERRABLE INITIALLY DEFERRED,
8148         resource_attr    INT            NOT NULL
8149                                         REFERENCES booking.resource_attr(id)
8150                                         ON DELETE CASCADE
8151                                         DEFERRABLE INITIALLY DEFERRED,
8152         value            INT            NOT NULL
8153                                         REFERENCES booking.resource_attr_value(id)
8154                                         DEFERRABLE INITIALLY DEFERRED,
8155         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
8156 );
8157
8158 CREATE TABLE booking.reservation (
8159         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
8160         start_time       TIMESTAMPTZ,
8161         end_time         TIMESTAMPTZ,
8162         capture_time     TIMESTAMPTZ,
8163         cancel_time      TIMESTAMPTZ,
8164         pickup_time      TIMESTAMPTZ,
8165         return_time      TIMESTAMPTZ,
8166         booking_interval INTERVAL,
8167         fine_interval    INTERVAL,
8168         fine_amount      DECIMAL(8,2),
8169         target_resource_type  INT       NOT NULL
8170                                         REFERENCES booking.resource_type(id)
8171                                         ON DELETE CASCADE
8172                                         DEFERRABLE INITIALLY DEFERRED,
8173         target_resource  INT            REFERENCES booking.resource(id)
8174                                         ON DELETE CASCADE
8175                                         DEFERRABLE INITIALLY DEFERRED,
8176         current_resource INT            REFERENCES booking.resource(id)
8177                                         ON DELETE CASCADE
8178                                         DEFERRABLE INITIALLY DEFERRED,
8179         request_lib      INT            NOT NULL
8180                                         REFERENCES actor.org_unit(id)
8181                                         DEFERRABLE INITIALLY DEFERRED,
8182         pickup_lib       INT            REFERENCES actor.org_unit(id)
8183                                         DEFERRABLE INITIALLY DEFERRED,
8184         capture_staff    INT            REFERENCES actor.usr(id)
8185                                         DEFERRABLE INITIALLY DEFERRED,
8186     max_fine         NUMERIC(8,2)
8187 ) INHERITS (money.billable_xact);
8188
8189 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
8190
8191 ALTER TABLE booking.reservation
8192         ADD CONSTRAINT booking_reservation_usr_fkey
8193         FOREIGN KEY (usr) REFERENCES actor.usr (id)
8194         DEFERRABLE INITIALLY DEFERRED;
8195
8196 CREATE TABLE booking.reservation_attr_value_map (
8197         id               SERIAL         PRIMARY KEY,
8198         reservation      INT            NOT NULL
8199                                         REFERENCES booking.reservation(id)
8200                                         ON DELETE CASCADE
8201                                         DEFERRABLE INITIALLY DEFERRED,
8202         attr_value       INT            NOT NULL
8203                                         REFERENCES booking.resource_attr_value(id)
8204                                         ON DELETE CASCADE
8205                                         DEFERRABLE INITIALLY DEFERRED,
8206         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
8207 );
8208
8209 -- represents a circ chain summary
8210 CREATE TYPE action.circ_chain_summary AS (
8211     num_circs INTEGER,
8212     start_time TIMESTAMP WITH TIME ZONE,
8213     checkout_workstation TEXT,
8214     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8215     last_stop_fines TEXT,
8216     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8217     last_renewal_workstation TEXT, -- NULL if no renewals
8218     last_checkin_workstation TEXT,
8219     last_checkin_time TIMESTAMP WITH TIME ZONE,
8220     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8221 );
8222
8223 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8224 DECLARE
8225     tmp_circ action.circulation%ROWTYPE;
8226     circ_0 action.circulation%ROWTYPE;
8227 BEGIN
8228
8229     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8230
8231     IF tmp_circ IS NULL THEN
8232         RETURN NEXT tmp_circ;
8233     END IF;
8234     circ_0 := tmp_circ;
8235
8236     -- find the front of the chain
8237     WHILE TRUE LOOP
8238         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8239         IF tmp_circ IS NULL THEN
8240             EXIT;
8241         END IF;
8242         circ_0 := tmp_circ;
8243     END LOOP;
8244
8245     -- now send the circs to the caller, oldest to newest
8246     tmp_circ := circ_0;
8247     WHILE TRUE LOOP
8248         IF tmp_circ IS NULL THEN
8249             EXIT;
8250         END IF;
8251         RETURN NEXT tmp_circ;
8252         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8253     END LOOP;
8254
8255 END;
8256 $$ LANGUAGE 'plpgsql';
8257
8258 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8259
8260 DECLARE
8261
8262     -- first circ in the chain
8263     circ_0 action.circulation%ROWTYPE;
8264
8265     -- last circ in the chain
8266     circ_n action.circulation%ROWTYPE;
8267
8268     -- circ chain under construction
8269     chain action.circ_chain_summary;
8270     tmp_circ action.circulation%ROWTYPE;
8271
8272 BEGIN
8273     
8274     chain.num_circs := 0;
8275     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8276
8277         IF chain.num_circs = 0 THEN
8278             circ_0 := tmp_circ;
8279         END IF;
8280
8281         chain.num_circs := chain.num_circs + 1;
8282         circ_n := tmp_circ;
8283     END LOOP;
8284
8285     chain.start_time := circ_0.xact_start;
8286     chain.last_stop_fines := circ_n.stop_fines;
8287     chain.last_stop_fines_time := circ_n.stop_fines_time;
8288     chain.last_checkin_time := circ_n.checkin_time;
8289     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8290     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8291     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8292
8293     IF chain.num_circs > 1 THEN
8294         chain.last_renewal_time := circ_n.xact_start;
8295         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8296     END IF;
8297
8298     RETURN chain;
8299
8300 END;
8301 $$ LANGUAGE 'plpgsql';
8302
8303 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8304 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8305 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8306
8307 ALTER TABLE config.standing_penalty
8308         ADD COLUMN org_depth   INTEGER;
8309
8310 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8311 DECLARE
8312     user_object         actor.usr%ROWTYPE;
8313     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8314     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8315     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8316     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8317     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8318     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8319     tmp_grp             INT;
8320     items_overdue       INT;
8321     items_out           INT;
8322     context_org_list    INT[];
8323     current_fines        NUMERIC(8,2) := 0.0;
8324     tmp_fines            NUMERIC(8,2);
8325     tmp_groc            RECORD;
8326     tmp_circ            RECORD;
8327     tmp_org             actor.org_unit%ROWTYPE;
8328     tmp_penalty         config.standing_penalty%ROWTYPE;
8329     tmp_depth           INTEGER;
8330 BEGIN
8331     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8332
8333     -- Max fines
8334     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8335
8336     -- Fail if the user has a high fine balance
8337     LOOP
8338         tmp_grp := user_object.profile;
8339         LOOP
8340             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8341
8342             IF max_fines.threshold IS NULL THEN
8343                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8344             ELSE
8345                 EXIT;
8346             END IF;
8347
8348             IF tmp_grp IS NULL THEN
8349                 EXIT;
8350             END IF;
8351         END LOOP;
8352
8353         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8354             EXIT;
8355         END IF;
8356
8357         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8358
8359     END LOOP;
8360
8361     IF max_fines.threshold IS NOT NULL THEN
8362
8363         FOR existing_sp_row IN
8364                 SELECT  *
8365                   FROM  actor.usr_standing_penalty
8366                   WHERE usr = match_user
8367                         AND org_unit = max_fines.org_unit
8368                         AND (stop_date IS NULL or stop_date > NOW())
8369                         AND standing_penalty = 1
8370                 LOOP
8371             RETURN NEXT existing_sp_row;
8372         END LOOP;
8373
8374         SELECT  SUM(f.balance_owed) INTO current_fines
8375           FROM  money.materialized_billable_xact_summary f
8376                 JOIN (
8377                     SELECT  r.id
8378                       FROM  booking.reservation r
8379                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8380                       WHERE usr = match_user
8381                             AND xact_finish IS NULL
8382                                 UNION ALL
8383                     SELECT  g.id
8384                       FROM  money.grocery g
8385                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8386                       WHERE usr = match_user
8387                             AND xact_finish IS NULL
8388                                 UNION ALL
8389                     SELECT  circ.id
8390                       FROM  action.circulation circ
8391                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8392                       WHERE usr = match_user
8393                             AND xact_finish IS NULL ) l USING (id);
8394
8395         IF current_fines >= max_fines.threshold THEN
8396             new_sp_row.usr := match_user;
8397             new_sp_row.org_unit := max_fines.org_unit;
8398             new_sp_row.standing_penalty := 1;
8399             RETURN NEXT new_sp_row;
8400         END IF;
8401     END IF;
8402
8403     -- Start over for max overdue
8404     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8405
8406     -- Fail if the user has too many overdue items
8407     LOOP
8408         tmp_grp := user_object.profile;
8409         LOOP
8410
8411             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8412
8413             IF max_overdue.threshold IS NULL THEN
8414                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8415             ELSE
8416                 EXIT;
8417             END IF;
8418
8419             IF tmp_grp IS NULL THEN
8420                 EXIT;
8421             END IF;
8422         END LOOP;
8423
8424         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8425             EXIT;
8426         END IF;
8427
8428         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8429
8430     END LOOP;
8431
8432     IF max_overdue.threshold IS NOT NULL THEN
8433
8434         FOR existing_sp_row IN
8435                 SELECT  *
8436                   FROM  actor.usr_standing_penalty
8437                   WHERE usr = match_user
8438                         AND org_unit = max_overdue.org_unit
8439                         AND (stop_date IS NULL or stop_date > NOW())
8440                         AND standing_penalty = 2
8441                 LOOP
8442             RETURN NEXT existing_sp_row;
8443         END LOOP;
8444
8445         SELECT  INTO items_overdue COUNT(*)
8446           FROM  action.circulation circ
8447                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8448           WHERE circ.usr = match_user
8449             AND circ.checkin_time IS NULL
8450             AND circ.due_date < NOW()
8451             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8452
8453         IF items_overdue >= max_overdue.threshold::INT THEN
8454             new_sp_row.usr := match_user;
8455             new_sp_row.org_unit := max_overdue.org_unit;
8456             new_sp_row.standing_penalty := 2;
8457             RETURN NEXT new_sp_row;
8458         END IF;
8459     END IF;
8460
8461     -- Start over for max out
8462     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8463
8464     -- Fail if the user has too many checked out items
8465     LOOP
8466         tmp_grp := user_object.profile;
8467         LOOP
8468             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8469
8470             IF max_items_out.threshold IS NULL THEN
8471                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8472             ELSE
8473                 EXIT;
8474             END IF;
8475
8476             IF tmp_grp IS NULL THEN
8477                 EXIT;
8478             END IF;
8479         END LOOP;
8480
8481         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8482             EXIT;
8483         END IF;
8484
8485         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8486
8487     END LOOP;
8488
8489
8490     -- Fail if the user has too many items checked out
8491     IF max_items_out.threshold IS NOT NULL THEN
8492
8493         FOR existing_sp_row IN
8494                 SELECT  *
8495                   FROM  actor.usr_standing_penalty
8496                   WHERE usr = match_user
8497                         AND org_unit = max_items_out.org_unit
8498                         AND (stop_date IS NULL or stop_date > NOW())
8499                         AND standing_penalty = 3
8500                 LOOP
8501             RETURN NEXT existing_sp_row;
8502         END LOOP;
8503
8504         SELECT  INTO items_out COUNT(*)
8505           FROM  action.circulation circ
8506                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8507           WHERE circ.usr = match_user
8508                 AND circ.checkin_time IS NULL
8509                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8510
8511            IF items_out >= max_items_out.threshold::INT THEN
8512             new_sp_row.usr := match_user;
8513             new_sp_row.org_unit := max_items_out.org_unit;
8514             new_sp_row.standing_penalty := 3;
8515             RETURN NEXT new_sp_row;
8516            END IF;
8517     END IF;
8518
8519     -- Start over for collections warning
8520     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8521
8522     -- Fail if the user has a collections-level fine balance
8523     LOOP
8524         tmp_grp := user_object.profile;
8525         LOOP
8526             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8527
8528             IF max_fines.threshold IS NULL THEN
8529                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8530             ELSE
8531                 EXIT;
8532             END IF;
8533
8534             IF tmp_grp IS NULL THEN
8535                 EXIT;
8536             END IF;
8537         END LOOP;
8538
8539         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8540             EXIT;
8541         END IF;
8542
8543         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8544
8545     END LOOP;
8546
8547     IF max_fines.threshold IS NOT NULL THEN
8548
8549         FOR existing_sp_row IN
8550                 SELECT  *
8551                   FROM  actor.usr_standing_penalty
8552                   WHERE usr = match_user
8553                         AND org_unit = max_fines.org_unit
8554                         AND (stop_date IS NULL or stop_date > NOW())
8555                         AND standing_penalty = 4
8556                 LOOP
8557             RETURN NEXT existing_sp_row;
8558         END LOOP;
8559
8560         SELECT  SUM(f.balance_owed) INTO current_fines
8561           FROM  money.materialized_billable_xact_summary f
8562                 JOIN (
8563                     SELECT  r.id
8564                       FROM  booking.reservation r
8565                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8566                       WHERE usr = match_user
8567                             AND xact_finish IS NULL
8568                                 UNION ALL
8569                     SELECT  g.id
8570                       FROM  money.grocery g
8571                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8572                       WHERE usr = match_user
8573                             AND xact_finish IS NULL
8574                                 UNION ALL
8575                     SELECT  circ.id
8576                       FROM  action.circulation circ
8577                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8578                       WHERE usr = match_user
8579                             AND xact_finish IS NULL ) l USING (id);
8580
8581         IF current_fines >= max_fines.threshold THEN
8582             new_sp_row.usr := match_user;
8583             new_sp_row.org_unit := max_fines.org_unit;
8584             new_sp_row.standing_penalty := 4;
8585             RETURN NEXT new_sp_row;
8586         END IF;
8587     END IF;
8588
8589     -- Start over for in collections
8590     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8591
8592     -- Remove the in-collections penalty if the user has paid down enough
8593     -- This penalty is different, because this code is not responsible for creating 
8594     -- new in-collections penalties, only for removing them
8595     LOOP
8596         tmp_grp := user_object.profile;
8597         LOOP
8598             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8599
8600             IF max_fines.threshold IS NULL THEN
8601                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8602             ELSE
8603                 EXIT;
8604             END IF;
8605
8606             IF tmp_grp IS NULL THEN
8607                 EXIT;
8608             END IF;
8609         END LOOP;
8610
8611         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8612             EXIT;
8613         END IF;
8614
8615         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8616
8617     END LOOP;
8618
8619     IF max_fines.threshold IS NOT NULL THEN
8620
8621         -- first, see if the user had paid down to the threshold
8622         SELECT  SUM(f.balance_owed) INTO current_fines
8623           FROM  money.materialized_billable_xact_summary f
8624                 JOIN (
8625                     SELECT  r.id
8626                       FROM  booking.reservation r
8627                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8628                       WHERE usr = match_user
8629                             AND xact_finish IS NULL
8630                                 UNION ALL
8631                     SELECT  g.id
8632                       FROM  money.grocery g
8633                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8634                       WHERE usr = match_user
8635                             AND xact_finish IS NULL
8636                                 UNION ALL
8637                     SELECT  circ.id
8638                       FROM  action.circulation circ
8639                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8640                       WHERE usr = match_user
8641                             AND xact_finish IS NULL ) l USING (id);
8642
8643         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8644             -- patron has paid down enough
8645
8646             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8647
8648             IF tmp_penalty.org_depth IS NOT NULL THEN
8649
8650                 -- since this code is not responsible for applying the penalty, it can't 
8651                 -- guarantee the current context org will match the org at which the penalty 
8652                 --- was applied.  search up the org tree until we hit the configured penalty depth
8653                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8654                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8655
8656                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8657
8658                     FOR existing_sp_row IN
8659                             SELECT  *
8660                             FROM  actor.usr_standing_penalty
8661                             WHERE usr = match_user
8662                                     AND org_unit = tmp_org.id
8663                                     AND (stop_date IS NULL or stop_date > NOW())
8664                                     AND standing_penalty = 30 
8665                             LOOP
8666
8667                         -- Penalty exists, return it for removal
8668                         RETURN NEXT existing_sp_row;
8669                     END LOOP;
8670
8671                     IF tmp_org.parent_ou IS NULL THEN
8672                         EXIT;
8673                     END IF;
8674
8675                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8676                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8677                 END LOOP;
8678
8679             ELSE
8680
8681                 -- no penalty depth is defined, look for exact matches
8682
8683                 FOR existing_sp_row IN
8684                         SELECT  *
8685                         FROM  actor.usr_standing_penalty
8686                         WHERE usr = match_user
8687                                 AND org_unit = max_fines.org_unit
8688                                 AND (stop_date IS NULL or stop_date > NOW())
8689                                 AND standing_penalty = 30 
8690                         LOOP
8691                     -- Penalty exists, return it for removal
8692                     RETURN NEXT existing_sp_row;
8693                 END LOOP;
8694             END IF;
8695     
8696         END IF;
8697
8698     END IF;
8699
8700     RETURN;
8701 END;
8702 $func$ LANGUAGE plpgsql;
8703
8704 -- Create a default row in acq.fiscal_calendar
8705 -- Add a column in actor.org_unit to point to it
8706
8707 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8708
8709 ALTER TABLE actor.org_unit
8710 ADD COLUMN fiscal_calendar INT NOT NULL
8711         REFERENCES acq.fiscal_calendar( id )
8712         DEFERRABLE INITIALLY DEFERRED
8713         DEFAULT 1;
8714
8715 ALTER TABLE auditor.actor_org_unit_history
8716         ADD COLUMN fiscal_calendar INT;
8717
8718 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8719
8720 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8721
8722 ALTER TABLE acq.funding_source_credit
8723 ADD COLUMN deadline_date TIMESTAMPTZ;
8724
8725 ALTER TABLE acq.funding_source_credit
8726 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8727
8728 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8729
8730 CREATE TABLE acq.fund_transfer (
8731         id               SERIAL         PRIMARY KEY,
8732         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8733                                         DEFERRABLE INITIALLY DEFERRED,
8734         src_amount       NUMERIC        NOT NULL,
8735         dest_fund        INT            REFERENCES acq.fund( id )
8736                                         DEFERRABLE INITIALLY DEFERRED,
8737         dest_amount      NUMERIC,
8738         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
8739         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
8740                                         DEFERRABLE INITIALLY DEFERRED,
8741         note             TEXT,
8742     funding_source_credit INTEGER   NOT NULL
8743                                         REFERENCES acq.funding_source_credit(id)
8744                                         DEFERRABLE INITIALLY DEFERRED
8745 );
8746
8747 CREATE INDEX acqftr_usr_idx
8748 ON acq.fund_transfer( transfer_user );
8749
8750 COMMENT ON TABLE acq.fund_transfer IS $$
8751 /*
8752  * Copyright (C) 2009  Georgia Public Library Service
8753  * Scott McKellar <scott@esilibrary.com>
8754  *
8755  * Fund Transfer
8756  *
8757  * Each row represents the transfer of money from a source fund
8758  * to a destination fund.  There should be corresponding entries
8759  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
8760  * to record how much money moved from which fund to which other
8761  * fund.
8762  * 
8763  * The presence of two amount fields, rather than one, reflects
8764  * the possibility that the two funds are denominated in different
8765  * currencies.  If they use the same currency type, the two
8766  * amounts should be the same.
8767  *
8768  * ****
8769  *
8770  * This program is free software; you can redistribute it and/or
8771  * modify it under the terms of the GNU General Public License
8772  * as published by the Free Software Foundation; either version 2
8773  * of the License, or (at your option) any later version.
8774  *
8775  * This program is distributed in the hope that it will be useful,
8776  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8777  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
8778  * GNU General Public License for more details.
8779  */
8780 $$;
8781
8782 CREATE TABLE acq.claim_event_type (
8783         id             SERIAL           PRIMARY KEY,
8784         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8785                                                  DEFERRABLE INITIALLY DEFERRED,
8786         code           TEXT             NOT NULL,
8787         description    TEXT             NOT NULL,
8788         library_initiated BOOL          NOT NULL DEFAULT FALSE,
8789         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
8790 );
8791
8792 CREATE TABLE acq.claim_event (
8793         id             BIGSERIAL        PRIMARY KEY,
8794         type           INT              NOT NULL REFERENCES acq.claim_event_type
8795                                                  DEFERRABLE INITIALLY DEFERRED,
8796         claim          SERIAL           NOT NULL REFERENCES acq.claim
8797                                                  DEFERRABLE INITIALLY DEFERRED,
8798         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
8799         creator        INT              NOT NULL REFERENCES actor.usr
8800                                                  DEFERRABLE INITIALLY DEFERRED,
8801         note           TEXT
8802 );
8803
8804 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
8805
8806 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
8807         src_usr  IN INTEGER,
8808         dest_usr IN INTEGER
8809 ) RETURNS VOID AS $$
8810 DECLARE
8811         suffix TEXT;
8812         renamable_row RECORD;
8813 BEGIN
8814
8815         UPDATE actor.usr SET
8816                 active = FALSE,
8817                 card = NULL,
8818                 mailing_address = NULL,
8819                 billing_address = NULL
8820         WHERE id = src_usr;
8821
8822         -- acq.*
8823         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
8824         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
8825         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
8826         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
8827         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
8828         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
8829         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
8830
8831         -- Update with a rename to avoid collisions
8832         FOR renamable_row in
8833                 SELECT id, name
8834                 FROM   acq.picklist
8835                 WHERE  owner = src_usr
8836         LOOP
8837                 suffix := ' (' || src_usr || ')';
8838                 LOOP
8839                         BEGIN
8840                                 UPDATE  acq.picklist
8841                                 SET     owner = dest_usr, name = name || suffix
8842                                 WHERE   id = renamable_row.id;
8843                         EXCEPTION WHEN unique_violation THEN
8844                                 suffix := suffix || ' ';
8845                                 CONTINUE;
8846                         END;
8847                         EXIT;
8848                 END LOOP;
8849         END LOOP;
8850
8851         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
8852         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
8853         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
8854         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
8855         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
8856         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
8857         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
8858         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
8859
8860         -- action.*
8861         DELETE FROM action.circulation WHERE usr = src_usr;
8862         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
8863         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
8864         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
8865         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
8866         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
8867         DELETE FROM action.hold_request WHERE usr = src_usr;
8868         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
8869         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
8870         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
8871         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
8872         DELETE FROM action.survey_response WHERE usr = src_usr;
8873         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
8874
8875         -- actor.*
8876         DELETE FROM actor.card WHERE usr = src_usr;
8877         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
8878
8879         -- The following update is intended to avoid transient violations of a foreign
8880         -- key constraint, whereby actor.usr_address references itself.  It may not be
8881         -- necessary, but it does no harm.
8882         UPDATE actor.usr_address SET replaces = NULL
8883                 WHERE usr = src_usr AND replaces IS NOT NULL;
8884         DELETE FROM actor.usr_address WHERE usr = src_usr;
8885         DELETE FROM actor.usr_note WHERE usr = src_usr;
8886         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
8887         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
8888         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
8889         DELETE FROM actor.usr_setting WHERE usr = src_usr;
8890         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
8891         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
8892
8893         -- asset.*
8894         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
8895         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
8896         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
8897         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
8898         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
8899         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
8900
8901         -- auditor.*
8902         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
8903         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
8904         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
8905         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
8906         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
8907         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
8908         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
8909         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
8910
8911         -- biblio.*
8912         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
8913         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
8914         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
8915         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
8916
8917         -- container.*
8918         -- Update buckets with a rename to avoid collisions
8919         FOR renamable_row in
8920                 SELECT id, name
8921                 FROM   container.biblio_record_entry_bucket
8922                 WHERE  owner = src_usr
8923         LOOP
8924                 suffix := ' (' || src_usr || ')';
8925                 LOOP
8926                         BEGIN
8927                                 UPDATE  container.biblio_record_entry_bucket
8928                                 SET     owner = dest_usr, name = name || suffix
8929                                 WHERE   id = renamable_row.id;
8930                         EXCEPTION WHEN unique_violation THEN
8931                                 suffix := suffix || ' ';
8932                                 CONTINUE;
8933                         END;
8934                         EXIT;
8935                 END LOOP;
8936         END LOOP;
8937
8938         FOR renamable_row in
8939                 SELECT id, name
8940                 FROM   container.call_number_bucket
8941                 WHERE  owner = src_usr
8942         LOOP
8943                 suffix := ' (' || src_usr || ')';
8944                 LOOP
8945                         BEGIN
8946                                 UPDATE  container.call_number_bucket
8947                                 SET     owner = dest_usr, name = name || suffix
8948                                 WHERE   id = renamable_row.id;
8949                         EXCEPTION WHEN unique_violation THEN
8950                                 suffix := suffix || ' ';
8951                                 CONTINUE;
8952                         END;
8953                         EXIT;
8954                 END LOOP;
8955         END LOOP;
8956
8957         FOR renamable_row in
8958                 SELECT id, name
8959                 FROM   container.copy_bucket
8960                 WHERE  owner = src_usr
8961         LOOP
8962                 suffix := ' (' || src_usr || ')';
8963                 LOOP
8964                         BEGIN
8965                                 UPDATE  container.copy_bucket
8966                                 SET     owner = dest_usr, name = name || suffix
8967                                 WHERE   id = renamable_row.id;
8968                         EXCEPTION WHEN unique_violation THEN
8969                                 suffix := suffix || ' ';
8970                                 CONTINUE;
8971                         END;
8972                         EXIT;
8973                 END LOOP;
8974         END LOOP;
8975
8976         FOR renamable_row in
8977                 SELECT id, name
8978                 FROM   container.user_bucket
8979                 WHERE  owner = src_usr
8980         LOOP
8981                 suffix := ' (' || src_usr || ')';
8982                 LOOP
8983                         BEGIN
8984                                 UPDATE  container.user_bucket
8985                                 SET     owner = dest_usr, name = name || suffix
8986                                 WHERE   id = renamable_row.id;
8987                         EXCEPTION WHEN unique_violation THEN
8988                                 suffix := suffix || ' ';
8989                                 CONTINUE;
8990                         END;
8991                         EXIT;
8992                 END LOOP;
8993         END LOOP;
8994
8995         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
8996
8997         -- money.*
8998         DELETE FROM money.billable_xact WHERE usr = src_usr;
8999         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9000         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9001
9002         -- permission.*
9003         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9004         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9005         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9006         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9007
9008         -- reporter.*
9009         -- Update with a rename to avoid collisions
9010         BEGIN
9011                 FOR renamable_row in
9012                         SELECT id, name
9013                         FROM   reporter.output_folder
9014                         WHERE  owner = src_usr
9015                 LOOP
9016                         suffix := ' (' || src_usr || ')';
9017                         LOOP
9018                                 BEGIN
9019                                         UPDATE  reporter.output_folder
9020                                         SET     owner = dest_usr, name = name || suffix
9021                                         WHERE   id = renamable_row.id;
9022                                 EXCEPTION WHEN unique_violation THEN
9023                                         suffix := suffix || ' ';
9024                                         CONTINUE;
9025                                 END;
9026                                 EXIT;
9027                         END LOOP;
9028                 END LOOP;
9029         EXCEPTION WHEN undefined_table THEN
9030                 -- do nothing
9031         END;
9032
9033         BEGIN
9034                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9035         EXCEPTION WHEN undefined_table THEN
9036                 -- do nothing
9037         END;
9038
9039         -- Update with a rename to avoid collisions
9040         BEGIN
9041                 FOR renamable_row in
9042                         SELECT id, name
9043                         FROM   reporter.report_folder
9044                         WHERE  owner = src_usr
9045                 LOOP
9046                         suffix := ' (' || src_usr || ')';
9047                         LOOP
9048                                 BEGIN
9049                                         UPDATE  reporter.report_folder
9050                                         SET     owner = dest_usr, name = name || suffix
9051                                         WHERE   id = renamable_row.id;
9052                                 EXCEPTION WHEN unique_violation THEN
9053                                         suffix := suffix || ' ';
9054                                         CONTINUE;
9055                                 END;
9056                                 EXIT;
9057                         END LOOP;
9058                 END LOOP;
9059         EXCEPTION WHEN undefined_table THEN
9060                 -- do nothing
9061         END;
9062
9063         BEGIN
9064                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9065         EXCEPTION WHEN undefined_table THEN
9066                 -- do nothing
9067         END;
9068
9069         BEGIN
9070                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9071         EXCEPTION WHEN undefined_table THEN
9072                 -- do nothing
9073         END;
9074
9075         -- Update with a rename to avoid collisions
9076         BEGIN
9077                 FOR renamable_row in
9078                         SELECT id, name
9079                         FROM   reporter.template_folder
9080                         WHERE  owner = src_usr
9081                 LOOP
9082                         suffix := ' (' || src_usr || ')';
9083                         LOOP
9084                                 BEGIN
9085                                         UPDATE  reporter.template_folder
9086                                         SET     owner = dest_usr, name = name || suffix
9087                                         WHERE   id = renamable_row.id;
9088                                 EXCEPTION WHEN unique_violation THEN
9089                                         suffix := suffix || ' ';
9090                                         CONTINUE;
9091                                 END;
9092                                 EXIT;
9093                         END LOOP;
9094                 END LOOP;
9095         EXCEPTION WHEN undefined_table THEN
9096         -- do nothing
9097         END;
9098
9099         -- vandelay.*
9100         -- Update with a rename to avoid collisions
9101         FOR renamable_row in
9102                 SELECT id, name
9103                 FROM   vandelay.queue
9104                 WHERE  owner = src_usr
9105         LOOP
9106                 suffix := ' (' || src_usr || ')';
9107                 LOOP
9108                         BEGIN
9109                                 UPDATE  vandelay.queue
9110                                 SET     owner = dest_usr, name = name || suffix
9111                                 WHERE   id = renamable_row.id;
9112                         EXCEPTION WHEN unique_violation THEN
9113                                 suffix := suffix || ' ';
9114                                 CONTINUE;
9115                         END;
9116                         EXIT;
9117                 END LOOP;
9118         END LOOP;
9119
9120 END;
9121 $$ LANGUAGE plpgsql;
9122
9123 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9124 /**
9125  * Finds rows dependent on a given row in actor.usr and either deletes them
9126  * or reassigns them to a different user.
9127  */
9128 $$;
9129
9130 CREATE OR REPLACE FUNCTION actor.usr_delete(
9131         src_usr  IN INTEGER,
9132         dest_usr IN INTEGER
9133 ) RETURNS VOID AS $$
9134 DECLARE
9135         old_profile actor.usr.profile%type;
9136         old_home_ou actor.usr.home_ou%type;
9137         new_profile actor.usr.profile%type;
9138         new_home_ou actor.usr.home_ou%type;
9139         new_name    text;
9140         new_dob     actor.usr.dob%type;
9141 BEGIN
9142         SELECT
9143                 id || '-PURGED-' || now(),
9144                 profile,
9145                 home_ou,
9146                 dob
9147         INTO
9148                 new_name,
9149                 old_profile,
9150                 old_home_ou,
9151                 new_dob
9152         FROM
9153                 actor.usr
9154         WHERE
9155                 id = src_usr;
9156         --
9157         -- Quit if no such user
9158         --
9159         IF old_profile IS NULL THEN
9160                 RETURN;
9161         END IF;
9162         --
9163         perform actor.usr_purge_data( src_usr, dest_usr );
9164         --
9165         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9166         -- could assume that there is only one root.  Theoretically, someday, maybe,
9167         -- there could be multiple roots, so we take extra trouble to get the right ones.
9168         --
9169         SELECT
9170                 id
9171         INTO
9172                 new_profile
9173         FROM
9174                 permission.grp_ancestors( old_profile )
9175         WHERE
9176                 parent is null;
9177         --
9178         SELECT
9179                 id
9180         INTO
9181                 new_home_ou
9182         FROM
9183                 actor.org_unit_ancestors( old_home_ou )
9184         WHERE
9185                 parent_ou is null;
9186         --
9187         -- Truncate date of birth
9188         --
9189         IF new_dob IS NOT NULL THEN
9190                 new_dob := date_trunc( 'year', new_dob );
9191         END IF;
9192         --
9193         UPDATE
9194                 actor.usr
9195                 SET
9196                         card = NULL,
9197                         profile = new_profile,
9198                         usrname = new_name,
9199                         email = NULL,
9200                         passwd = random()::text,
9201                         standing = DEFAULT,
9202                         ident_type = 
9203                         (
9204                                 SELECT MIN( id )
9205                                 FROM config.identification_type
9206                         ),
9207                         ident_value = NULL,
9208                         ident_type2 = NULL,
9209                         ident_value2 = NULL,
9210                         net_access_level = DEFAULT,
9211                         photo_url = NULL,
9212                         prefix = NULL,
9213                         first_given_name = new_name,
9214                         second_given_name = NULL,
9215                         family_name = new_name,
9216                         suffix = NULL,
9217                         alias = NULL,
9218                         day_phone = NULL,
9219                         evening_phone = NULL,
9220                         other_phone = NULL,
9221                         mailing_address = NULL,
9222                         billing_address = NULL,
9223                         home_ou = new_home_ou,
9224                         dob = new_dob,
9225                         active = FALSE,
9226                         master_account = DEFAULT, 
9227                         super_user = DEFAULT,
9228                         barred = FALSE,
9229                         deleted = TRUE,
9230                         juvenile = DEFAULT,
9231                         usrgroup = 0,
9232                         claims_returned_count = DEFAULT,
9233                         credit_forward_balance = DEFAULT,
9234                         last_xact_id = DEFAULT,
9235                         alert_message = NULL,
9236                         create_date = now(),
9237                         expire_date = now()
9238         WHERE
9239                 id = src_usr;
9240 END;
9241 $$ LANGUAGE plpgsql;
9242
9243 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9244 /**
9245  * Logically deletes a user.  Removes personally identifiable information,
9246  * and purges associated data in other tables.
9247  */
9248 $$;
9249
9250 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9251
9252 ALTER TABLE acq.fund
9253 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9254
9255 ALTER TABLE acq.fund
9256         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9257
9258 -- A fund can't roll over if it doesn't propagate from one year to the next
9259
9260 ALTER TABLE acq.fund
9261         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9262         ( propagate OR NOT rollover );
9263
9264 ALTER TABLE acq.fund
9265         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9266
9267 ALTER TABLE acq.fund
9268     ADD COLUMN balance_warning_percent INT
9269     CONSTRAINT balance_warning_percent_limit
9270         CHECK( balance_warning_percent <= 100 );
9271
9272 ALTER TABLE acq.fund
9273     ADD COLUMN balance_stop_percent INT
9274     CONSTRAINT balance_stop_percent_limit
9275         CHECK( balance_stop_percent <= 100 );
9276
9277 CREATE VIEW acq.ordered_funding_source_credit AS
9278         SELECT
9279                 CASE WHEN deadline_date IS NULL THEN
9280                         2
9281                 ELSE
9282                         1
9283                 END AS sort_priority,
9284                 CASE WHEN deadline_date IS NULL THEN
9285                         effective_date
9286                 ELSE
9287                         deadline_date
9288                 END AS sort_date,
9289                 id,
9290                 funding_source,
9291                 amount,
9292                 note
9293         FROM
9294                 acq.funding_source_credit;
9295
9296 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9297 /*
9298  * Copyright (C) 2009  Georgia Public Library Service
9299  * Scott McKellar <scott@gmail.com>
9300  *
9301  * The acq.ordered_funding_source_credit view is a prioritized
9302  * ordering of funding source credits.  When ordered by the first
9303  * three columns, this view defines the order in which the various
9304  * credits are to be tapped for spending, subject to the allocations
9305  * in the acq.fund_allocation table.
9306  *
9307  * The first column reflects the principle that we should spend
9308  * money with deadlines before spending money without deadlines.
9309  *
9310  * The second column reflects the principle that we should spend the
9311  * oldest money first.  For money with deadlines, that means that we
9312  * spend first from the credit with the earliest deadline.  For
9313  * money without deadlines, we spend first from the credit with the
9314  * earliest effective date.  
9315  *
9316  * The third column is a tie breaker to ensure a consistent
9317  * ordering.
9318  *
9319  * ****
9320  *
9321  * This program is free software; you can redistribute it and/or
9322  * modify it under the terms of the GNU General Public License
9323  * as published by the Free Software Foundation; either version 2
9324  * of the License, or (at your option) any later version.
9325  *
9326  * This program is distributed in the hope that it will be useful,
9327  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9328  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9329  * GNU General Public License for more details.
9330  */
9331 $$;
9332
9333 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9334     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9335       FROM  money.materialized_billable_xact_summary m
9336             LEFT JOIN action.circulation c ON (c.id = m.id)
9337             LEFT JOIN money.grocery g ON (g.id = m.id)
9338             LEFT JOIN booking.reservation r ON (r.id = m.id);
9339
9340 CREATE TABLE config.marc21_rec_type_map (
9341     code        TEXT    PRIMARY KEY,
9342     type_val    TEXT    NOT NULL,
9343     blvl_val    TEXT    NOT NULL
9344 );
9345
9346 CREATE TABLE config.marc21_ff_pos_map (
9347     id          SERIAL  PRIMARY KEY,
9348     fixed_field TEXT    NOT NULL,
9349     tag         TEXT    NOT NULL,
9350     rec_type    TEXT    NOT NULL,
9351     start_pos   INT     NOT NULL,
9352     length      INT     NOT NULL,
9353     default_val TEXT    NOT NULL DEFAULT ' '
9354 );
9355
9356 CREATE TABLE config.marc21_physical_characteristic_type_map (
9357     ptype_key   TEXT    PRIMARY KEY,
9358     label       TEXT    NOT NULL -- I18N
9359 );
9360
9361 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9362     id          SERIAL  PRIMARY KEY,
9363     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9364     subfield    TEXT    NOT NULL,
9365     start_pos   INT     NOT NULL,
9366     length      INT     NOT NULL,
9367     label       TEXT    NOT NULL -- I18N
9368 );
9369
9370 CREATE TABLE config.marc21_physical_characteristic_value_map (
9371     id              SERIAL  PRIMARY KEY,
9372     value           TEXT    NOT NULL,
9373     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9374     label           TEXT    NOT NULL -- I18N
9375 );
9376
9377 ----------------------------------
9378 -- MARC21 record structure data --
9379 ----------------------------------
9380
9381 -- Record type map
9382 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9383 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9384 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9385 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9386 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9387 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9388 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9389 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9390
9391 ------ Physical Characteristics
9392
9393 -- Map
9394 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9395 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9396 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9397 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9398 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9399 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9400 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9401 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');
9402 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9403 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9404 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9405 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9406 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9407 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');
9408 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9409 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9410 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9411 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9412 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9413 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9414 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9415 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9416 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9417 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9418 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');
9419 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');
9420 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');
9421 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');
9422 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9423 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');
9424 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9425 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9426 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9427 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');
9428 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9429 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9430 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
9431 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');
9432 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9433 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');
9434 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
9435 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9436 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9437 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9438 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9439 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9440 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9441 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');
9442
9443 -- Electronic Resource
9444 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9445 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9446 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');
9447 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');
9448 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');
9449 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');
9450 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');
9451 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');
9452 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');
9453 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');
9454 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9455 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9456 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9457 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
9458 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');
9459 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');
9460 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9461 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');
9462 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9463 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');
9464 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9465 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9466 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9467 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.');
9468 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.');
9469 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.');
9470 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.');
9471 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.');
9472 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');
9473 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.');
9474 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9475 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.');
9476 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9477 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
9478 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)');
9479 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
9480 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9481 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9482 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9483 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9484 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');
9485 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9486 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');
9487 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');
9488 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9489 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9490 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9491 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');
9492 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
9493 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9494 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
9495 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');
9496 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');
9497 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');
9498 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)');
9499 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9500 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');
9501 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9502 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9503 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9504 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9505 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9506 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9507 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9508 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
9509 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9510 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');
9511 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9512 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9513 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9514
9515 -- Globe
9516 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9517 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9518 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');
9519 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');
9520 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');
9521 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');
9522 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9523 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9524 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
9525 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');
9526 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9527 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9528 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9529 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9530 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9531 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9532 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9533 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9534 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9535 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9536 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9537 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9538 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9539 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9540 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');
9541 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9542 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9543
9544 -- Tactile Material
9545 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9546 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9547 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9548 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9549 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9550 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');
9551 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9552 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9553 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9554 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');
9555 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');
9556 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');
9557 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');
9558 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');
9559 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');
9560 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');
9561 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9562 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9563 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9564 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9565 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9566 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9567 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');
9568 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9569 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9570 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9571 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');
9572 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');
9573 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');
9574 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9575 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');
9576 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');
9577 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');
9578 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');
9579 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');
9580 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');
9581 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9582 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');
9583 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');
9584 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9585 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9586 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
9587 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');
9588 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');
9589 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');
9590 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9591 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9592
9593 -- Projected Graphic
9594 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9595 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9596 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');
9597 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9598 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');
9599 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');
9600 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9601 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
9602 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9603 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9604 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');
9605 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9606 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');
9607 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9608 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9609 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9610 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9611 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9612 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9613 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9614 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');
9615 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');
9616 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');
9617 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9618 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9619 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9620 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');
9621 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');
9622 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');
9623 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9624 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9625 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');
9626 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');
9627 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');
9628 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');
9629 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');
9630 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');
9631 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');
9632 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9633 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9634 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9635 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9636 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9637 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.');
9638 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.');
9639 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.');
9640 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.');
9641 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.');
9642 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.');
9643 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.');
9644 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.)');
9645 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.)');
9646 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.)');
9647 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.)');
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 x 10 in. (21 x 26 cm.)');
9650 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.)');
9651 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.)');
9652 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.)');
9653 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9654 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9655 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9656 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9657 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9658 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9659 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');
9660 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');
9661 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');
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_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9664
9665 -- Microform
9666 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9667 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9668 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');
9669 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');
9670 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');
9671 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');
9672 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9673 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');
9674 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9675 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9676 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9677 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9678 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9679 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
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 ('h','e','4','1','Dimensions');
9683 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.');
9684 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.');
9685 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.');
9686 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9687 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.');
9688 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.)');
9689 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.)');
9690 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.)');
9691 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.)');
9692 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9693 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9694 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');
9695 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)');
9696 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)');
9697 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)');
9698 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)');
9699 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-)');
9700 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9701 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');
9702 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9703 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');
9704 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9705 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9706 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9707 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9708 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9709 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');
9710 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9711 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9712 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9713 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');
9714 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9715 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9716 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
9717 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');
9718 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');
9719 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');
9720 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');
9721 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9722 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9723 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');
9724 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');
9725 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');
9726 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');
9727 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');
9728 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');
9729 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');
9730 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');
9731 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');
9732 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9733 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9734
9735 -- Non-projected Graphic
9736 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9737 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
9738 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
9739 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
9740 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
9741 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');
9742 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
9743 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
9744 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
9745 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
9746 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');
9747 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
9748 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');
9749 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9750 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9751 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
9752 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');
9753 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');
9754 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9755 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');
9756 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9757 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9758 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9759 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
9760 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9761 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');
9762 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');
9763 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9764 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9765 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9766 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9767 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9768 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9769 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9770 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9771 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9772 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9773 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9774 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9775 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9776 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 ('k','f','5','1','Secondary support material');
9778 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9779 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');
9780 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');
9781 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9782 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9783 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9784 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9785 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9786 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');
9787 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9788 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9789 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9790 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9791 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9792 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9793 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9794 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9795
9796 -- Motion Picture
9797 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
9798 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
9799 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');
9800 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');
9801 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');
9802 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9803 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9804 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
9805 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');
9806 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9807 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');
9808 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9809 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9810 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9811 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
9812 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');
9813 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)');
9814 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
9815 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)');
9816 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');
9817 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');
9818 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9819 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9820 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');
9821 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');
9822 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');
9823 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9824 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
9825 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');
9826 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');
9827 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');
9828 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');
9829 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');
9830 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');
9831 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');
9832 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9833 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9834 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9835 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9836 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
9837 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.');
9838 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.');
9839 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.');
9840 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.');
9841 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.');
9842 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.');
9843 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.');
9844 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9845 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9846 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
9847 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9848 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
9849 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');
9850 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');
9851 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
9852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9853 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9854 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
9855 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');
9856 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
9857 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
9858 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
9859 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');
9860 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');
9861 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');
9862 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');
9863 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9864
9865 -- Remote-sensing Image
9866 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
9867 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
9868 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9869 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
9870 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
9871 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
9872 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
9873 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');
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 ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9876 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
9877 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');
9878 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');
9879 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
9880 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');
9881 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9882 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
9883 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%');
9884 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%');
9885 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%');
9886 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%');
9887 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%');
9888 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%');
9889 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%');
9890 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%');
9891 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%');
9892 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%');
9893 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');
9894 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9895 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
9896 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
9897 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');
9898 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');
9899 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');
9900 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');
9901 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');
9902 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');
9903 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');
9904 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');
9905 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');
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 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
9909 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
9910 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');
9911 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');
9912 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed uses');
9913 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');
9914 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9915 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9916 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
9917 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
9918 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
9919 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9920 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9921 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
9922 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');
9923 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');
9924 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');
9925 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');
9926 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');
9927 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)');
9928 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');
9929 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
9930 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');
9931 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)');
9932 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)');
9933 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)');
9934 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');
9935 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');
9936 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');
9937 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');
9938 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');
9939 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');
9940 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');
9941 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');
9942 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');
9943 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');
9944 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');
9945 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');
9946 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');
9947 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');
9948 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');
9949 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');
9950 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');
9951 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');
9952 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');
9953 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');
9954 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');
9955 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)');
9956 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');
9957 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
9958 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
9959 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');
9960 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');
9961 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9962 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9963
9964 -- Sound Recording
9965 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
9966 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
9967 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');
9968 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
9969 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');
9970 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');
9971 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
9972 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');
9973 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');
9974 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9975 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');
9976 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9977 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
9978 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');
9979 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');
9980 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');
9981 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');
9982 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');
9983 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');
9984 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');
9985 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');
9986 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');
9987 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');
9988 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');
9989 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');
9990 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');
9991 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');
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 ('s','e','4','1','Configuration of playback channels');
9995 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
9996 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
9997 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
9998 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9999 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10000 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10001 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');
10002 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');
10003 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');
10004 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10005 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10006 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10007 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.');
10008 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.');
10009 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.');
10010 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.');
10011 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.');
10012 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.');
10013 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.)');
10014 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.');
10015 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');
10016 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.');
10017 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.');
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 ('s','h','7','1','Tape width');
10021 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.');
10022 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.');
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 ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1/2 in.');
10025 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.');
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 ('s','i','8','1','Tape configuration ');
10029 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');
10030 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');
10031 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');
10032 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');
10033 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');
10034 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');
10035 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');
10036 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
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 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10039 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');
10040 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');
10041 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');
10042 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');
10043 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');
10044 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');
10045 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');
10046 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');
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 ('s','n','13','1','Capture and storage');
10051 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');
10052 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');
10053 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');
10054 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');
10055 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10056 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10057
10058 -- Videorecording
10059 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10060 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10061 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10062 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10063 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10064 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10065 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10066 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10067 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10068 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');
10069 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10070 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10071 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10072 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10073 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10074 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10075 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10076 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10077 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');
10078 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10079 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');
10080 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10081 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10082 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10083 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10084 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');
10085 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');
10086 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');
10087 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');
10088 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.');
10089 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.');
10090 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10091 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10092 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10093 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');
10094 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');
10095 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');
10096 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10097 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10098 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');
10099 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');
10100 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');
10101 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');
10102 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');
10103 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');
10104 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');
10105 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10106 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10107 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10108 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10109 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10110 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.');
10111 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.');
10112 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.');
10113 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.');
10114 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.');
10115 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.');
10116 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10117 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10118 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10119 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10120 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10121 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');
10122 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');
10123 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10124 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10125 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10126
10127 -- Fixed Field position data -- 0-based!
10128 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10129 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10130 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10131 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10132 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10133 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10134 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10135 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10136 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10137 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10138 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10139 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10140 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10141 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10142 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10143 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10144 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10145 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10146 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10147 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10148 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10149 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10150 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10151 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10152 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10153 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10154 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10155 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10156 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10157 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10158 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10159 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10160 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10161 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10162 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10163 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10164 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10165 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10166 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10167 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10168 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10169 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10170 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10171 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10172 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10173 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10174 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10175 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10176 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10177 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10178 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10179 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10180 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10181 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10182 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10183 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10184 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10185 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10186 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10187 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10188 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10189 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10190 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10191 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10192 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10193 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10194 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10195 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10196 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10197 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10198 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10199 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10200 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10201 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10202 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10203 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10204 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10205 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10206 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10207 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10208 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10209 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10210 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10211 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10212 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10213 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10214 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10215 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10216 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10217 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10218 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10219 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10220 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10221 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10222 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10223 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10224 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10225 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10226 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10227 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10228 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10229 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10230 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10231 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10232 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10233 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10234 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10235 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10236 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10237 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10238 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10239 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10240 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10241 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10242 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10243 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10244 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10245 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10246 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10247 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10248 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10249 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10250 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10251 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10252 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10253 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10254 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10255 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10256 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10257 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10258 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10259 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10260 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10261 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10262 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');
10263 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');
10264 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10265 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10266 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10267 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10268 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10269 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10270 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10271 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10272 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10273 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10274
10275 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10276 DECLARE
10277         ldr         RECORD;
10278         tval        TEXT;
10279         tval_rec    RECORD;
10280         bval        TEXT;
10281         bval_rec    RECORD;
10282     retval      config.marc21_rec_type_map%ROWTYPE;
10283 BEGIN
10284     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10285
10286     IF ldr.id IS NULL THEN
10287         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10288         RETURN retval;
10289     END IF;
10290
10291     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10292     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10293
10294
10295     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10296     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10297
10298     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10299
10300     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10301
10302
10303     IF retval.code IS NULL THEN
10304         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10305     END IF;
10306
10307     RETURN retval;
10308 END;
10309 $func$ LANGUAGE PLPGSQL;
10310
10311 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10312 DECLARE
10313     rtype       TEXT;
10314     ff_pos      RECORD;
10315     tag_data    RECORD;
10316     val         TEXT;
10317 BEGIN
10318     rtype := (biblio.marc21_record_type( rid )).code;
10319     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10320         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10321             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10322             RETURN val;
10323         END LOOP;
10324         val := REPEAT( ff_pos.default_val, ff_pos.length );
10325         RETURN val;
10326     END LOOP;
10327
10328     RETURN NULL;
10329 END;
10330 $func$ LANGUAGE PLPGSQL;
10331
10332 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10333 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10334 DECLARE
10335     rowid   INT := 0;
10336     _007    RECORD;
10337     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10338     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10339     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10340     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10341 BEGIN
10342
10343     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10344
10345     IF _007.id IS NOT NULL THEN
10346         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10347
10348         IF ptype.ptype_key IS NOT NULL THEN
10349             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10350                 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 );
10351
10352                 IF pval.id IS NOT NULL THEN
10353                     rowid := rowid + 1;
10354                     retval.id := rowid;
10355                     retval.record := rid;
10356                     retval.ptype := ptype.ptype_key;
10357                     retval.subfield := psf.id;
10358                     retval.value := pval.id;
10359                     RETURN NEXT retval;
10360                 END IF;
10361
10362             END LOOP;
10363         END IF;
10364     END IF;
10365
10366     RETURN;
10367 END;
10368 $func$ LANGUAGE PLPGSQL;
10369
10370 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10371 DROP VIEW IF EXISTS money.open_usr_summary;
10372 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10373
10374 -- The view should supply defaults for numeric (amount) columns
10375 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10376     SELECT  xact.id,
10377         xact.usr,
10378         xact.xact_start,
10379         xact.xact_finish,
10380         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10381         credit.payment_ts AS last_payment_ts,
10382         credit.note AS last_payment_note,
10383         credit.payment_type AS last_payment_type,
10384         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10385         debit.billing_ts AS last_billing_ts,
10386         debit.note AS last_billing_note,
10387         debit.billing_type AS last_billing_type,
10388         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10389         p.relname AS xact_type
10390       FROM  money.billable_xact xact
10391         JOIN pg_class p ON xact.tableoid = p.oid
10392         LEFT JOIN (
10393             SELECT  billing.xact,
10394                 sum(billing.amount) AS amount,
10395                 max(billing.billing_ts) AS billing_ts,
10396                 last(billing.note) AS note,
10397                 last(billing.billing_type) AS billing_type
10398               FROM  money.billing
10399               WHERE billing.voided IS FALSE
10400               GROUP BY billing.xact
10401             ) debit ON xact.id = debit.xact
10402         LEFT JOIN (
10403             SELECT  payment_view.xact,
10404                 sum(payment_view.amount) AS amount,
10405                 max(payment_view.payment_ts) AS payment_ts,
10406                 last(payment_view.note) AS note,
10407                 last(payment_view.payment_type) AS payment_type
10408               FROM  money.payment_view
10409               WHERE payment_view.voided IS FALSE
10410               GROUP BY payment_view.xact
10411             ) credit ON xact.id = credit.xact
10412       ORDER BY debit.billing_ts, credit.payment_ts;
10413
10414 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10415     SELECT * FROM money.billable_xact_summary_location_view
10416     WHERE xact_finish IS NULL;
10417
10418 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10419     SELECT 
10420         usr,
10421         SUM(total_paid) AS total_paid,
10422         SUM(total_owed) AS total_owed,
10423         SUM(balance_owed) AS balance_owed
10424     FROM  money.materialized_billable_xact_summary
10425     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10426     GROUP BY usr;
10427
10428 CREATE OR REPLACE VIEW money.usr_summary AS
10429     SELECT 
10430         usr, 
10431         sum(total_paid) AS total_paid, 
10432         sum(total_owed) AS total_owed, 
10433         sum(balance_owed) AS balance_owed
10434     FROM money.materialized_billable_xact_summary
10435     GROUP BY usr;
10436
10437 CREATE OR REPLACE VIEW money.open_usr_summary AS
10438     SELECT 
10439         usr, 
10440         sum(total_paid) AS total_paid, 
10441         sum(total_owed) AS total_owed, 
10442         sum(balance_owed) AS balance_owed
10443     FROM money.materialized_billable_xact_summary
10444     WHERE xact_finish IS NULL
10445     GROUP BY usr;
10446
10447 -- 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;
10448
10449 CREATE TABLE config.biblio_fingerprint (
10450         id                      SERIAL  PRIMARY KEY,
10451         name            TEXT    NOT NULL, 
10452         xpath           TEXT    NOT NULL,
10453     first_word  BOOL    NOT NULL DEFAULT FALSE,
10454         format          TEXT    NOT NULL DEFAULT 'marcxml'
10455 );
10456
10457 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10458     VALUES (
10459         'Title',
10460         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10461             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10462             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10463             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10464             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10465         'marcxml'
10466     );
10467
10468 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10469     VALUES (
10470         'Author',
10471         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10472             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10473             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10474             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10475             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10476         'marcxml',
10477         TRUE
10478     );
10479
10480 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10481 DECLARE
10482     qual        INT;
10483     ldr         TEXT;
10484     tval        TEXT;
10485     tval_rec    RECORD;
10486     bval        TEXT;
10487     bval_rec    RECORD;
10488     type_map    RECORD;
10489     ff_pos      RECORD;
10490     ff_tag_data TEXT;
10491 BEGIN
10492
10493     IF marc IS NULL OR marc = '' THEN
10494         RETURN NULL;
10495     END IF;
10496
10497     -- First, the count of tags
10498     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10499
10500     -- now go through a bunch of pain to get the record type
10501     IF best_type IS NOT NULL THEN
10502         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10503
10504         IF ldr IS NOT NULL THEN
10505             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10506             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10507
10508
10509             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10510             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10511
10512             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10513
10514             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10515
10516             IF type_map.code IS NOT NULL THEN
10517                 IF best_type = type_map.code THEN
10518                     qual := qual + qual / 2;
10519                 END IF;
10520
10521                 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
10522                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10523                     IF ff_tag_data = best_lang THEN
10524                             qual := qual + 100;
10525                     END IF;
10526                 END LOOP;
10527             END IF;
10528         END IF;
10529     END IF;
10530
10531     -- Now look for some quality metrics
10532     -- DCL record?
10533     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10534         qual := qual + 10;
10535     END IF;
10536
10537     -- From OCLC?
10538     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10539         qual := qual + 10;
10540     END IF;
10541
10542     RETURN qual;
10543
10544 END;
10545 $func$ LANGUAGE PLPGSQL;
10546
10547 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10548 DECLARE
10549     idx     config.biblio_fingerprint%ROWTYPE;
10550     xfrm        config.xml_transform%ROWTYPE;
10551     prev_xfrm   TEXT;
10552     transformed_xml TEXT;
10553     xml_node    TEXT;
10554     xml_node_list   TEXT[];
10555     raw_text    TEXT;
10556     output_text TEXT := '';
10557 BEGIN
10558
10559     IF marc IS NULL OR marc = '' THEN
10560         RETURN NULL;
10561     END IF;
10562
10563     -- Loop over the indexing entries
10564     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10565
10566         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10567
10568         -- See if we can skip the XSLT ... it's expensive
10569         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10570             -- Can't skip the transform
10571             IF xfrm.xslt <> '---' THEN
10572                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10573             ELSE
10574                 transformed_xml := marc;
10575             END IF;
10576
10577             prev_xfrm := xfrm.name;
10578         END IF;
10579
10580         raw_text := COALESCE(
10581             naco_normalize(
10582                 ARRAY_TO_STRING(
10583                     oils_xpath(
10584                         '//text()',
10585                         (oils_xpath(
10586                             idx.xpath,
10587                             transformed_xml,
10588                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10589                         ))[1]
10590                     ),
10591                     ''
10592                 )
10593             ),
10594             ''
10595         );
10596
10597         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10598         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10599
10600         IF idx.first_word IS TRUE THEN
10601             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10602         END IF;
10603
10604         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10605
10606     END LOOP;
10607
10608     RETURN output_text;
10609
10610 END;
10611 $func$ LANGUAGE PLPGSQL;
10612
10613 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10614 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10615 BEGIN
10616
10617     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10618
10619     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10620         RETURN NEW;
10621     END IF;
10622
10623     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10624     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10625
10626     RETURN NEW;
10627
10628 END;
10629 $func$ LANGUAGE PLPGSQL;
10630
10631 CREATE TABLE config.internal_flag (
10632     name    TEXT    PRIMARY KEY,
10633     value   TEXT,
10634     enabled BOOL    NOT NULL DEFAULT FALSE
10635 );
10636 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10637 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10638 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10639 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10640 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10641 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10642 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10643 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10644 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10645 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10646
10647 CREATE TABLE authority.bib_linking (
10648     id          BIGSERIAL   PRIMARY KEY,
10649     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10650     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10651 );
10652 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10653 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10654
10655 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10656     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10657 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10658
10659 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10660     DELETE FROM authority.bib_linking WHERE bib = $1;
10661     INSERT INTO authority.bib_linking (bib, authority)
10662         SELECT  y.bib,
10663                 y.authority
10664           FROM (    SELECT  DISTINCT $1 AS bib,
10665                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10666                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10667                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10668                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10669     SELECT $1;
10670 $func$ LANGUAGE SQL;
10671
10672 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10673 BEGIN
10674     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10675     IF NOT FOUND THEN
10676         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10677     END IF;
10678     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)
10679         SELECT  bib_id,
10680                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10681                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10682                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10683                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10684                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10685                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10686                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10687                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10688                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10689                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10690                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10691                 (   SELECT  v.value
10692                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10693                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10694                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10695                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10696                 biblio.marc21_extract_fixed_field( bib_id, 'Date1'),
10697                 biblio.marc21_extract_fixed_field( bib_id, 'Date2');
10698
10699     RETURN;
10700 END;
10701 $func$ LANGUAGE PLPGSQL;
10702
10703 CREATE TABLE config.metabib_class (
10704     name    TEXT    PRIMARY KEY,
10705     label   TEXT    NOT NULL UNIQUE
10706 );
10707
10708 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10709 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10710 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10711 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10712 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10713
10714 CREATE TABLE metabib.facet_entry (
10715         id              BIGSERIAL       PRIMARY KEY,
10716         source          BIGINT          NOT NULL,
10717         field           INT             NOT NULL,
10718         value           TEXT            NOT NULL
10719 );
10720
10721 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10722 DECLARE
10723     fclass          RECORD;
10724     ind_data        metabib.field_entry_template%ROWTYPE;
10725 BEGIN
10726     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10727     IF NOT FOUND THEN
10728         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10729             -- RAISE NOTICE 'Emptying out %', fclass.name;
10730             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10731         END LOOP;
10732         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10733     END IF;
10734
10735     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10736         IF ind_data.field < 0 THEN
10737             ind_data.field = -1 * ind_data.field;
10738             INSERT INTO metabib.facet_entry (field, source, value)
10739                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10740         ELSE
10741             EXECUTE $$
10742                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10743                     VALUES ($$ ||
10744                         quote_literal(ind_data.field) || $$, $$ ||
10745                         quote_literal(ind_data.source) || $$, $$ ||
10746                         quote_literal(ind_data.value) ||
10747                     $$);$$;
10748         END IF;
10749
10750     END LOOP;
10751
10752     RETURN;
10753 END;
10754 $func$ LANGUAGE PLPGSQL;
10755
10756 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
10757 DECLARE
10758     uris            TEXT[];
10759     uri_xml         TEXT;
10760     uri_label       TEXT;
10761     uri_href        TEXT;
10762     uri_use         TEXT;
10763     uri_owner       TEXT;
10764     uri_owner_id    INT;
10765     uri_id          INT;
10766     uri_cn_id       INT;
10767     uri_map_id      INT;
10768 BEGIN
10769
10770     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
10771     IF ARRAY_UPPER(uris,1) > 0 THEN
10772         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
10773             -- First we pull info out of the 856
10774             uri_xml     := uris[i];
10775
10776             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
10777             CONTINUE WHEN uri_href IS NULL;
10778
10779             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10780             CONTINUE WHEN uri_label IS NULL;
10781
10782             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
10783             CONTINUE WHEN uri_owner IS NULL;
10784
10785             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()',uri_xml))[1];
10786
10787             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
10788
10789             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
10790             CONTINUE WHEN NOT FOUND;
10791
10792             -- now we look for a matching uri
10793             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10794             IF NOT FOUND THEN -- create one
10795                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
10796                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10797             END IF;
10798
10799             -- we need a call number to link through
10800             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;
10801             IF NOT FOUND THEN
10802                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
10803                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
10804                 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;
10805             END IF;
10806
10807             -- now, link them if they're not already
10808             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
10809             IF NOT FOUND THEN
10810                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
10811             END IF;
10812
10813         END LOOP;
10814     END IF;
10815
10816     RETURN;
10817 END;
10818 $func$ LANGUAGE PLPGSQL;
10819
10820 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
10821 DECLARE
10822     source_count    INT;
10823     old_mr          BIGINT;
10824     tmp_mr          metabib.metarecord%ROWTYPE;
10825     deleted_mrs     BIGINT[];
10826 BEGIN
10827
10828     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
10829
10830     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
10831
10832         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
10833             old_mr := tmp_mr.id;
10834         ELSE
10835             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
10836             IF source_count = 0 THEN -- No other records
10837                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
10838                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
10839             END IF;
10840         END IF;
10841
10842     END LOOP;
10843
10844     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
10845         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
10846         IF old_mr IS NULL THEN -- nope, create one and grab its id
10847             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
10848             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
10849         ELSE -- indeed there is. update it with a null cache and recalcualated master record
10850             UPDATE  metabib.metarecord
10851               SET   mods = NULL,
10852                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
10853               WHERE id = old_mr;
10854         END IF;
10855     ELSE -- there was one we already attached to, update its mods cache and master_record
10856         UPDATE  metabib.metarecord
10857           SET   mods = NULL,
10858                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
10859           WHERE id = old_mr;
10860     END IF;
10861
10862     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
10863
10864     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
10865         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
10866     END IF;
10867
10868     RETURN old_mr;
10869
10870 END;
10871 $func$ LANGUAGE PLPGSQL;
10872
10873 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
10874 BEGIN
10875     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10876     IF NOT FOUND THEN
10877         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
10878     END IF;
10879     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
10880         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
10881
10882     RETURN;
10883 END;
10884 $func$ LANGUAGE PLPGSQL;
10885
10886 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
10887 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
10888 BEGIN
10889
10890     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
10891         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
10892         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
10893         RETURN NEW; -- and we're done
10894     END IF;
10895
10896     IF TG_OP = 'UPDATE' THEN -- re-ingest?
10897         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
10898
10899         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
10900             RETURN NEW;
10901         END IF;
10902     END IF;
10903
10904     -- Record authority linking
10905     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
10906     IF NOT FOUND THEN
10907         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
10908     END IF;
10909
10910     -- Flatten and insert the mfr data
10911     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
10912     IF NOT FOUND THEN
10913         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
10914         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
10915         IF NOT FOUND THEN
10916             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
10917         END IF;
10918     END IF;
10919
10920     -- Gather and insert the field entry data
10921     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
10922
10923     -- Located URI magic
10924     IF TG_OP = 'INSERT' THEN
10925         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
10926         IF NOT FOUND THEN
10927             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
10928         END IF;
10929     ELSE
10930         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
10931         IF NOT FOUND THEN
10932             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
10933         END IF;
10934     END IF;
10935
10936     -- (re)map metarecord-bib linking
10937     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
10938         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
10939         IF NOT FOUND THEN
10940             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
10941         END IF;
10942     ELSE -- we're doing an update, and we're not deleted, remap
10943         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
10944         IF NOT FOUND THEN
10945             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
10946         END IF;
10947     END IF;
10948
10949     RETURN NEW;
10950 END;
10951 $func$ LANGUAGE PLPGSQL;
10952
10953 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
10954 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 ();
10955
10956 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
10957
10958 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT )
10959 RETURNS SETOF RECORD AS $func$
10960 DECLARE
10961     xpath_list  TEXT[];
10962     select_list TEXT[];
10963     where_list  TEXT[];
10964     q           TEXT;
10965     out_record  RECORD;
10966     empty_test  RECORD;
10967 BEGIN
10968     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
10969
10970     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
10971
10972     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
10973         select_list := ARRAY_APPEND(
10974             select_list,
10975             $sel$
10976             EXPLODE_ARRAY(
10977                 COALESCE(
10978                     NULLIF(
10979                         oils_xpath(
10980                             $sel$ ||
10981                                 quote_literal(
10982                                     CASE
10983                                         WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
10984                                         ELSE xpath_list[i] || '//text()'
10985                                     END
10986                                 ) ||
10987                             $sel$,
10988                             $sel$ || document_field || $sel$
10989                         ),
10990                        '{}'::TEXT[]
10991                     ),
10992                     '{NULL}'::TEXT[]
10993                 )
10994             ) AS c_$sel$ || i
10995         );
10996         where_list := ARRAY_APPEND(
10997             where_list,
10998             'c_' || i || ' IS NOT NULL'
10999         );
11000     END LOOP;
11001
11002     q := $q$
11003 SELECT * FROM (
11004     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11005 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
11006     -- RAISE NOTICE 'query: %', q;
11007
11008     FOR out_record IN EXECUTE q LOOP
11009         RETURN NEXT out_record;
11010     END LOOP;
11011
11012     RETURN;
11013 END;
11014 $func$ LANGUAGE PLPGSQL;
11015
11016 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11017 DECLARE
11018
11019     owning_lib      TEXT;
11020     circ_lib        TEXT;
11021     call_number     TEXT;
11022     copy_number     TEXT;
11023     status          TEXT;
11024     location        TEXT;
11025     circulate       TEXT;
11026     deposit         TEXT;
11027     deposit_amount  TEXT;
11028     ref             TEXT;
11029     holdable        TEXT;
11030     price           TEXT;
11031     barcode         TEXT;
11032     circ_modifier   TEXT;
11033     circ_as_type    TEXT;
11034     alert_message   TEXT;
11035     opac_visible    TEXT;
11036     pub_note        TEXT;
11037     priv_note       TEXT;
11038
11039     attr_def        RECORD;
11040     tmp_attr_set    RECORD;
11041     attr_set        vandelay.import_item%ROWTYPE;
11042
11043     xpath           TEXT;
11044
11045 BEGIN
11046
11047     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11048
11049     IF FOUND THEN
11050
11051         attr_set.definition := attr_def.id; 
11052     
11053         -- Build the combined XPath
11054     
11055         owning_lib :=
11056             CASE
11057                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11058                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11059                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11060             END;
11061     
11062         circ_lib :=
11063             CASE
11064                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11065                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11066                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11067             END;
11068     
11069         call_number :=
11070             CASE
11071                 WHEN attr_def.call_number IS NULL THEN 'null()'
11072                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11073                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11074             END;
11075     
11076         copy_number :=
11077             CASE
11078                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11079                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11080                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11081             END;
11082     
11083         status :=
11084             CASE
11085                 WHEN attr_def.status IS NULL THEN 'null()'
11086                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11087                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11088             END;
11089     
11090         location :=
11091             CASE
11092                 WHEN attr_def.location IS NULL THEN 'null()'
11093                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11094                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11095             END;
11096     
11097         circulate :=
11098             CASE
11099                 WHEN attr_def.circulate IS NULL THEN 'null()'
11100                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11101                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11102             END;
11103     
11104         deposit :=
11105             CASE
11106                 WHEN attr_def.deposit IS NULL THEN 'null()'
11107                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11108                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11109             END;
11110     
11111         deposit_amount :=
11112             CASE
11113                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11114                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11115                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11116             END;
11117     
11118         ref :=
11119             CASE
11120                 WHEN attr_def.ref IS NULL THEN 'null()'
11121                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11122                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11123             END;
11124     
11125         holdable :=
11126             CASE
11127                 WHEN attr_def.holdable IS NULL THEN 'null()'
11128                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11129                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11130             END;
11131     
11132         price :=
11133             CASE
11134                 WHEN attr_def.price IS NULL THEN 'null()'
11135                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11136                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11137             END;
11138     
11139         barcode :=
11140             CASE
11141                 WHEN attr_def.barcode IS NULL THEN 'null()'
11142                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11143                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11144             END;
11145     
11146         circ_modifier :=
11147             CASE
11148                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11149                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11150                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11151             END;
11152     
11153         circ_as_type :=
11154             CASE
11155                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11156                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11157                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11158             END;
11159     
11160         alert_message :=
11161             CASE
11162                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11163                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11164                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11165             END;
11166     
11167         opac_visible :=
11168             CASE
11169                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11170                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11171                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11172             END;
11173
11174         pub_note :=
11175             CASE
11176                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11177                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11178                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11179             END;
11180         priv_note :=
11181             CASE
11182                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11183                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11184                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11185             END;
11186     
11187     
11188         xpath := 
11189             owning_lib      || '|' || 
11190             circ_lib        || '|' || 
11191             call_number     || '|' || 
11192             copy_number     || '|' || 
11193             status          || '|' || 
11194             location        || '|' || 
11195             circulate       || '|' || 
11196             deposit         || '|' || 
11197             deposit_amount  || '|' || 
11198             ref             || '|' || 
11199             holdable        || '|' || 
11200             price           || '|' || 
11201             barcode         || '|' || 
11202             circ_modifier   || '|' || 
11203             circ_as_type    || '|' || 
11204             alert_message   || '|' || 
11205             pub_note        || '|' || 
11206             priv_note       || '|' || 
11207             opac_visible;
11208
11209         -- RAISE NOTICE 'XPath: %', xpath;
11210         
11211         FOR tmp_attr_set IN
11212                 SELECT  *
11213                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11214                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11215                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11216                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11217         LOOP
11218     
11219             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11220             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11221
11222             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11223             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11224     
11225             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11226             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11227             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11228     
11229             SELECT  id INTO attr_set.location
11230               FROM  asset.copy_location
11231               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11232                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11233     
11234             attr_set.circulate      :=
11235                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11236                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11237
11238             attr_set.deposit        :=
11239                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11240                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11241
11242             attr_set.holdable       :=
11243                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11244                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11245
11246             attr_set.opac_visible   :=
11247                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11248                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11249
11250             attr_set.ref            :=
11251                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11252                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11253     
11254             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11255             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11256             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11257     
11258             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11259             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11260             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11261             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11262             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11263             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11264             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11265             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11266     
11267             RETURN NEXT attr_set;
11268     
11269         END LOOP;
11270     
11271     END IF;
11272
11273     RETURN;
11274
11275 END;
11276 $$ LANGUAGE PLPGSQL;
11277
11278 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11279 DECLARE
11280     attr_def    BIGINT;
11281     item_data   vandelay.import_item%ROWTYPE;
11282 BEGIN
11283
11284     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11285
11286     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11287         INSERT INTO vandelay.import_item (
11288             record,
11289             definition,
11290             owning_lib,
11291             circ_lib,
11292             call_number,
11293             copy_number,
11294             status,
11295             location,
11296             circulate,
11297             deposit,
11298             deposit_amount,
11299             ref,
11300             holdable,
11301             price,
11302             barcode,
11303             circ_modifier,
11304             circ_as_type,
11305             alert_message,
11306             pub_note,
11307             priv_note,
11308             opac_visible
11309         ) VALUES (
11310             NEW.id,
11311             item_data.definition,
11312             item_data.owning_lib,
11313             item_data.circ_lib,
11314             item_data.call_number,
11315             item_data.copy_number,
11316             item_data.status,
11317             item_data.location,
11318             item_data.circulate,
11319             item_data.deposit,
11320             item_data.deposit_amount,
11321             item_data.ref,
11322             item_data.holdable,
11323             item_data.price,
11324             item_data.barcode,
11325             item_data.circ_modifier,
11326             item_data.circ_as_type,
11327             item_data.alert_message,
11328             item_data.pub_note,
11329             item_data.priv_note,
11330             item_data.opac_visible
11331         );
11332     END LOOP;
11333
11334     RETURN NULL;
11335 END;
11336 $func$ LANGUAGE PLPGSQL;
11337
11338 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11339 BEGIN
11340     EXECUTE $$
11341         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11342     $$;
11343         RETURN TRUE;
11344 END;
11345 $creator$ LANGUAGE 'plpgsql';
11346
11347 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11348 BEGIN
11349     EXECUTE $$
11350         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11351             audit_id    BIGINT                          PRIMARY KEY,
11352             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11353             audit_action        TEXT                            NOT NULL,
11354             LIKE $$ || sch || $$.$$ || tbl || $$
11355         );
11356     $$;
11357         RETURN TRUE;
11358 END;
11359 $creator$ LANGUAGE 'plpgsql';
11360
11361 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11362 BEGIN
11363     EXECUTE $$
11364         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11365         RETURNS TRIGGER AS $func$
11366         BEGIN
11367             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11368                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11369                     now(),
11370                     SUBSTR(TG_OP,1,1),
11371                     OLD.*;
11372             RETURN NULL;
11373         END;
11374         $func$ LANGUAGE 'plpgsql';
11375     $$;
11376         RETURN TRUE;
11377 END;
11378 $creator$ LANGUAGE 'plpgsql';
11379
11380 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11381 BEGIN
11382     EXECUTE $$
11383         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11384             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11385             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11386     $$;
11387         RETURN TRUE;
11388 END;
11389 $creator$ LANGUAGE 'plpgsql';
11390
11391 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11392 BEGIN
11393     EXECUTE $$
11394         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11395             SELECT      -1, now() as audit_time, '-' as audit_action, *
11396               FROM      $$ || sch || $$.$$ || tbl || $$
11397                 UNION ALL
11398             SELECT      *
11399               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11400     $$;
11401         RETURN TRUE;
11402 END;
11403 $creator$ LANGUAGE 'plpgsql';
11404
11405 -- The main event
11406
11407 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11408 BEGIN
11409     PERFORM acq.create_acq_seq(sch, tbl);
11410     PERFORM acq.create_acq_history(sch, tbl);
11411     PERFORM acq.create_acq_func(sch, tbl);
11412     PERFORM acq.create_acq_update_trigger(sch, tbl);
11413     PERFORM acq.create_acq_lifecycle(sch, tbl);
11414     RETURN TRUE;
11415 END;
11416 $creator$ LANGUAGE 'plpgsql';
11417
11418 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
11419 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
11420
11421 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11422
11423 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
11424 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
11425
11426 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11427     SELECT  fund.id AS fund,
11428             fund_debit.encumbrance AS encumbrance,
11429             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11430       FROM acq.fund AS fund
11431                         LEFT JOIN acq.fund_debit AS fund_debit
11432                                 ON ( fund.id = fund_debit.fund )
11433       GROUP BY 1,2;
11434
11435 CREATE TABLE acq.debit_attribution (
11436         id                     INT         NOT NULL PRIMARY KEY,
11437         fund_debit             INT         NOT NULL
11438                                            REFERENCES acq.fund_debit
11439                                            DEFERRABLE INITIALLY DEFERRED,
11440     debit_amount           NUMERIC     NOT NULL,
11441         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11442                                            DEFERRABLE INITIALLY DEFERRED,
11443     credit_amount          NUMERIC
11444 );
11445
11446 CREATE INDEX acq_attribution_debit_idx
11447         ON acq.debit_attribution( fund_debit );
11448
11449 CREATE INDEX acq_attribution_credit_idx
11450         ON acq.debit_attribution( funding_source_credit );
11451
11452 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11453 /*
11454 Function to attribute expenditures and encumbrances to funding source credits,
11455 and thereby to funding sources.
11456
11457 Read the debits in chonological order, attributing each one to one or
11458 more funding source credits.  Constraints:
11459
11460 1. Don't attribute more to a credit than the amount of the credit.
11461
11462 2. For a given fund, don't attribute more to a funding source than the
11463 source has allocated to that fund.
11464
11465 3. Attribute debits to credits with deadlines before attributing them to
11466 credits without deadlines.  Otherwise attribute to the earliest credits
11467 first, based on the deadline date when present, or on the effective date
11468 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11469 This ordering is defined by an ORDER BY clause on the view
11470 acq.ordered_funding_source_credit.
11471
11472 Start by truncating the table acq.debit_attribution.  Then insert a row
11473 into that table for each attribution.  If a debit cannot be fully
11474 attributed, insert a row for the unattributable balance, with the 
11475 funding_source_credit and credit_amount columns NULL.
11476 */
11477 DECLARE
11478         curr_fund_source_bal RECORD;
11479         seqno                INT;     -- sequence num for credits applicable to a fund
11480         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11481         fc                   RECORD;  -- used for loading t_fund_credit table
11482         sc                   RECORD;  -- used for loading t_fund_credit table
11483         --
11484         -- Used exclusively in the main loop:
11485         --
11486         deb                 RECORD;   -- current row from acq.fund_debit table
11487         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11488         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11489         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11490         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11491         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11492         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11493         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11494         attrib_count        INT;      -- populates id of acq.debit_attribution
11495 BEGIN
11496         --
11497         -- Load a temporary table.  For each combination of fund and funding source,
11498         -- load an entry with the total amount allocated to that fund by that source.
11499         -- This sum may reflect transfers as well as original allocations.  We will
11500         -- reduce this balance whenever we attribute debits to it.
11501         --
11502         CREATE TEMP TABLE t_fund_source_bal
11503         ON COMMIT DROP AS
11504                 SELECT
11505                         fund AS fund,
11506                         funding_source AS source,
11507                         sum( amount ) AS balance
11508                 FROM
11509                         acq.fund_allocation
11510                 GROUP BY
11511                         fund,
11512                         funding_source
11513                 HAVING
11514                         sum( amount ) > 0;
11515         --
11516         CREATE INDEX t_fund_source_bal_idx
11517                 ON t_fund_source_bal( fund, source );
11518         -------------------------------------------------------------------------------
11519         --
11520         -- Load another temporary table.  For each fund, load zero or more
11521         -- funding source credits from which that fund can get money.
11522         --
11523         CREATE TEMP TABLE t_fund_credit (
11524                 fund        INT,
11525                 seq         INT,
11526                 credit      INT
11527         ) ON COMMIT DROP;
11528         --
11529         FOR fc IN
11530                 SELECT DISTINCT fund
11531                 FROM acq.fund_allocation
11532                 ORDER BY fund
11533         LOOP                  -- Loop over the funds
11534                 seqno := 1;
11535                 FOR sc IN
11536                         SELECT
11537                                 ofsc.id
11538                         FROM
11539                                 acq.ordered_funding_source_credit AS ofsc
11540                         WHERE
11541                                 ofsc.funding_source IN
11542                                 (
11543                                         SELECT funding_source
11544                                         FROM acq.fund_allocation
11545                                         WHERE fund = fc.fund
11546                                 )
11547                 ORDER BY
11548                     ofsc.sort_priority,
11549                     ofsc.sort_date,
11550                     ofsc.id
11551                 LOOP                        -- Add each credit to the list
11552                         INSERT INTO t_fund_credit (
11553                                 fund,
11554                                 seq,
11555                                 credit
11556                         ) VALUES (
11557                                 fc.fund,
11558                                 seqno,
11559                                 sc.id
11560                         );
11561                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11562                         seqno := seqno + 1;
11563                 END LOOP;     -- Loop over credits for a given fund
11564         END LOOP;         -- Loop over funds
11565         --
11566         CREATE INDEX t_fund_credit_idx
11567                 ON t_fund_credit( fund, seq );
11568         -------------------------------------------------------------------------------
11569         --
11570         -- Load yet another temporary table.  This one is a list of funding source
11571         -- credits, with their balances.  We shall reduce those balances as we
11572         -- attribute debits to them.
11573         --
11574         CREATE TEMP TABLE t_credit
11575         ON COMMIT DROP AS
11576         SELECT
11577             fsc.id AS credit,
11578             fsc.funding_source AS source,
11579             fsc.amount AS balance,
11580             fs.currency_type AS currency_type
11581         FROM
11582             acq.funding_source_credit AS fsc,
11583             acq.funding_source fs
11584         WHERE
11585             fsc.funding_source = fs.id
11586                         AND fsc.amount > 0;
11587         --
11588         CREATE INDEX t_credit_idx
11589                 ON t_credit( credit );
11590         --
11591         -------------------------------------------------------------------------------
11592         --
11593         -- Now that we have loaded the lookup tables: loop through the debits,
11594         -- attributing each one to one or more funding source credits.
11595         -- 
11596         truncate table acq.debit_attribution;
11597         --
11598         attrib_count := 0;
11599         FOR deb in
11600                 SELECT
11601                         fd.id,
11602                         fd.fund,
11603                         fd.amount,
11604                         f.currency_type,
11605                         fd.encumbrance
11606                 FROM
11607                         acq.fund_debit fd,
11608                         acq.fund f
11609                 WHERE
11610                         fd.fund = f.id
11611                 ORDER BY
11612                         fd.id
11613         LOOP
11614                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11615                 --
11616                 debit_balance := deb.amount;
11617                 --
11618                 -- Loop over the funding source credits that are eligible
11619                 -- to pay for this debit
11620                 --
11621                 FOR fund_credit IN
11622                         SELECT
11623                                 credit
11624                         FROM
11625                                 t_fund_credit
11626                         WHERE
11627                                 fund = deb.fund
11628                         ORDER BY
11629                                 seq
11630                 LOOP
11631                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11632                         --
11633                         -- Look up the balance for this credit.  If it's zero, then
11634                         -- it's not useful, so treat it as if you didn't find it.
11635                         -- (Actually there shouldn't be any zero balances in the table,
11636                         -- but we check just to make sure.)
11637                         --
11638                         SELECT *
11639                         INTO curr_credit_bal
11640                         FROM t_credit
11641                         WHERE
11642                                 credit = fund_credit.credit
11643                                 AND balance > 0;
11644                         --
11645                         IF curr_credit_bal IS NULL THEN
11646                                 --
11647                                 -- This credit is exhausted; try the next one.
11648                                 --
11649                                 CONTINUE;
11650                         END IF;
11651                         --
11652                         --
11653                         -- At this point we have an applicable credit with some money left.
11654                         -- Now see if the relevant funding_source has any money left.
11655                         --
11656                         -- Look up the balance of the allocation for this combination of
11657                         -- fund and source.  If you find such an entry, but it has a zero
11658                         -- balance, then it's not useful, so treat it as unfound.
11659                         -- (Actually there shouldn't be any zero balances in the table,
11660                         -- but we check just to make sure.)
11661                         --
11662                         SELECT *
11663                         INTO curr_fund_source_bal
11664                         FROM t_fund_source_bal
11665                         WHERE
11666                                 fund = deb.fund
11667                                 AND source = curr_credit_bal.source
11668                                 AND balance > 0;
11669                         --
11670                         IF curr_fund_source_bal IS NULL THEN
11671                                 --
11672                                 -- This fund/source doesn't exist or is already exhausted,
11673                                 -- so we can't use this credit.  Go on to the next one.
11674                                 --
11675                                 CONTINUE;
11676                         END IF;
11677                         --
11678                         -- Convert the available balances to the currency of the fund
11679                         --
11680                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11681                                 curr_credit_bal.currency_type, deb.currency_type );
11682                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11683                                 curr_credit_bal.currency_type, deb.currency_type );
11684                         --
11685                         -- Determine how much we can attribute to this credit: the minimum
11686                         -- of the debit amount, the fund/source balance, and the
11687                         -- credit balance
11688                         --
11689                         --RAISE NOTICE '   deb bal %', debit_balance;
11690                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11691                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11692                         --
11693                         conv_attr_amount := NULL;
11694                         attr_amount := debit_balance;
11695                         --
11696                         IF attr_amount > conv_alloc_balance THEN
11697                                 attr_amount := conv_alloc_balance;
11698                                 conv_attr_amount := curr_fund_source_bal.balance;
11699                         END IF;
11700                         IF attr_amount > conv_cred_balance THEN
11701                                 attr_amount := conv_cred_balance;
11702                                 conv_attr_amount := curr_credit_bal.balance;
11703                         END IF;
11704                         --
11705                         -- If we're attributing all of one of the balances, then that's how
11706                         -- much we will deduct from the balances, and we already captured
11707                         -- that amount above.  Otherwise we must convert the amount of the
11708                         -- attribution from the currency of the fund back to the currency of
11709                         -- the funding source.
11710                         --
11711                         IF conv_attr_amount IS NULL THEN
11712                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11713                                         deb.currency_type, curr_credit_bal.currency_type );
11714                         END IF;
11715                         --
11716                         -- Insert a row to record the attribution
11717                         --
11718                         attrib_count := attrib_count + 1;
11719                         INSERT INTO acq.debit_attribution (
11720                                 id,
11721                                 fund_debit,
11722                                 debit_amount,
11723                                 funding_source_credit,
11724                                 credit_amount
11725                         ) VALUES (
11726                                 attrib_count,
11727                                 deb.id,
11728                                 attr_amount,
11729                                 curr_credit_bal.credit,
11730                                 conv_attr_amount
11731                         );
11732                         --
11733                         -- Subtract the attributed amount from the various balances
11734                         --
11735                         debit_balance := debit_balance - attr_amount;
11736                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11737                         --
11738                         IF curr_fund_source_bal.balance <= 0 THEN
11739                                 --
11740                                 -- This allocation is exhausted.  Delete it so
11741                                 -- that we don't waste time looking at it again.
11742                                 --
11743                                 DELETE FROM t_fund_source_bal
11744                                 WHERE
11745                                         fund = curr_fund_source_bal.fund
11746                                         AND source = curr_fund_source_bal.source;
11747                         ELSE
11748                                 UPDATE t_fund_source_bal
11749                                 SET balance = balance - conv_attr_amount
11750                                 WHERE
11751                                         fund = curr_fund_source_bal.fund
11752                                         AND source = curr_fund_source_bal.source;
11753                         END IF;
11754                         --
11755                         IF curr_credit_bal.balance <= 0 THEN
11756                                 --
11757                                 -- This funding source credit is exhausted.  Delete it
11758                                 -- so that we don't waste time looking at it again.
11759                                 --
11760                                 --DELETE FROM t_credit
11761                                 --WHERE
11762                                 --      credit = curr_credit_bal.credit;
11763                                 --
11764                                 DELETE FROM t_fund_credit
11765                                 WHERE
11766                                         credit = curr_credit_bal.credit;
11767                         ELSE
11768                                 UPDATE t_credit
11769                                 SET balance = curr_credit_bal.balance
11770                                 WHERE
11771                                         credit = curr_credit_bal.credit;
11772                         END IF;
11773                         --
11774                         -- Are we done with this debit yet?
11775                         --
11776                         IF debit_balance <= 0 THEN
11777                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
11778                         END IF;
11779                 END LOOP;       -- End loop over credits
11780                 --
11781                 IF debit_balance <> 0 THEN
11782                         --
11783                         -- We weren't able to attribute this debit, or at least not
11784                         -- all of it.  Insert a row for the unattributed balance.
11785                         --
11786                         attrib_count := attrib_count + 1;
11787                         INSERT INTO acq.debit_attribution (
11788                                 id,
11789                                 fund_debit,
11790                                 debit_amount,
11791                                 funding_source_credit,
11792                                 credit_amount
11793                         ) VALUES (
11794                                 attrib_count,
11795                                 deb.id,
11796                                 debit_balance,
11797                                 NULL,
11798                                 NULL
11799                         );
11800                 END IF;
11801         END LOOP;   -- End of loop over debits
11802 END;
11803 $$ LANGUAGE 'plpgsql';
11804
11805 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
11806 DECLARE
11807     query TEXT;
11808     output TEXT;
11809 BEGIN
11810     query := $q$
11811         SELECT  regexp_replace(
11812                     oils_xpath_string(
11813                         $q$ || quote_literal($3) || $q$,
11814                         marc,
11815                         ' '
11816                     ),
11817                     $q$ || quote_literal($4) || $q$,
11818                     '',
11819                     'g')
11820           FROM  $q$ || $1 || $q$
11821           WHERE id = $q$ || $2;
11822
11823     EXECUTE query INTO output;
11824
11825     -- RAISE NOTICE 'query: %, output; %', query, output;
11826
11827     RETURN output;
11828 END;
11829 $$ LANGUAGE PLPGSQL IMMUTABLE;
11830
11831 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
11832     SELECT extract_marc_field($1,$2,$3,'');
11833 $$ LANGUAGE SQL IMMUTABLE;
11834
11835 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
11836 DECLARE
11837     moved_objects INT := 0;
11838     source_cn     asset.call_number%ROWTYPE;
11839     target_cn     asset.call_number%ROWTYPE;
11840     metarec       metabib.metarecord%ROWTYPE;
11841     hold          action.hold_request%ROWTYPE;
11842     ser_rec       serial.record_entry%ROWTYPE;
11843     uri_count     INT := 0;
11844     counter       INT := 0;
11845     uri_datafield TEXT;
11846     uri_text      TEXT := '';
11847 BEGIN
11848
11849     -- move any 856 entries on records that have at least one MARC-mapped URI entry
11850     SELECT  INTO uri_count COUNT(*)
11851       FROM  asset.uri_call_number_map m
11852             JOIN asset.call_number cn ON (m.call_number = cn.id)
11853       WHERE cn.record = source_record;
11854
11855     IF uri_count > 0 THEN
11856
11857         SELECT  COUNT(*) INTO counter
11858           FROM  oils_xpath_table(
11859                     'id',
11860                     'marc',
11861                     'biblio.record_entry',
11862                     '//*[@tag="856"]',
11863                     'id=' || source_record
11864                 ) as t(i int,c text);
11865
11866         FOR i IN 1 .. counter LOOP
11867             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
11868                         ' tag="856"' || 
11869                         ' ind1="' || FIRST(ind1) || '"'  || 
11870                         ' ind2="' || FIRST(ind2) || '">' || 
11871                         array_to_string(
11872                             array_accum(
11873                                 '<subfield code="' || subfield || '">' ||
11874                                 regexp_replace(
11875                                     regexp_replace(
11876                                         regexp_replace(data,'&','&amp;','g'),
11877                                         '>', '&gt;', 'g'
11878                                     ),
11879                                     '<', '&lt;', 'g'
11880                                 ) || '</subfield>'
11881                             ), ''
11882                         ) || '</datafield>' INTO uri_datafield
11883               FROM  oils_xpath_table(
11884                         'id',
11885                         'marc',
11886                         'biblio.record_entry',
11887                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
11888                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
11889                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
11890                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
11891                         'id=' || source_record
11892                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
11893
11894             uri_text := uri_text || uri_datafield;
11895         END LOOP;
11896
11897         IF uri_text <> '' THEN
11898             UPDATE  biblio.record_entry
11899               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
11900               WHERE id = target_record;
11901         END IF;
11902
11903     END IF;
11904
11905     -- Find and move metarecords to the target record
11906     SELECT  INTO metarec *
11907       FROM  metabib.metarecord
11908       WHERE master_record = source_record;
11909
11910     IF FOUND THEN
11911         UPDATE  metabib.metarecord
11912           SET   master_record = target_record,
11913             mods = NULL
11914           WHERE id = metarec.id;
11915
11916         moved_objects := moved_objects + 1;
11917     END IF;
11918
11919     -- Find call numbers attached to the source ...
11920     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
11921
11922         SELECT  INTO target_cn *
11923           FROM  asset.call_number
11924           WHERE label = source_cn.label
11925             AND owning_lib = source_cn.owning_lib
11926             AND record = target_record;
11927
11928         -- ... and if there's a conflicting one on the target ...
11929         IF FOUND THEN
11930
11931             -- ... move the copies to that, and ...
11932             UPDATE  asset.copy
11933               SET   call_number = target_cn.id
11934               WHERE call_number = source_cn.id;
11935
11936             -- ... move V holds to the move-target call number
11937             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
11938
11939                 UPDATE  action.hold_request
11940                   SET   target = target_cn.id
11941                   WHERE id = hold.id;
11942
11943                 moved_objects := moved_objects + 1;
11944             END LOOP;
11945
11946         -- ... if not ...
11947         ELSE
11948             -- ... just move the call number to the target record
11949             UPDATE  asset.call_number
11950               SET   record = target_record
11951               WHERE id = source_cn.id;
11952         END IF;
11953
11954         moved_objects := moved_objects + 1;
11955     END LOOP;
11956
11957     -- Find T holds targeting the source record ...
11958     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
11959
11960         -- ... and move them to the target record
11961         UPDATE  action.hold_request
11962           SET   target = target_record
11963           WHERE id = hold.id;
11964
11965         moved_objects := moved_objects + 1;
11966     END LOOP;
11967
11968     -- Find serial records targeting the source record ...
11969     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
11970         -- ... and move them to the target record
11971         UPDATE  serial.record_entry
11972           SET   record = target_record
11973           WHERE id = ser_rec.id;
11974
11975         moved_objects := moved_objects + 1;
11976     END LOOP;
11977
11978     -- Finally, "delete" the source record
11979     DELETE FROM biblio.record_entry WHERE id = source_record;
11980
11981     -- That's all, folks!
11982     RETURN moved_objects;
11983 END;
11984 $func$ LANGUAGE plpgsql;
11985
11986 CREATE OR REPLACE FUNCTION acq.transfer_fund(
11987         old_fund   IN INT,
11988         old_amount IN NUMERIC,     -- in currency of old fund
11989         new_fund   IN INT,
11990         new_amount IN NUMERIC,     -- in currency of new fund
11991         user_id    IN INT,
11992         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
11993         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
11994 ) RETURNS VOID AS $$
11995 /* -------------------------------------------------------------------------------
11996
11997 Function to transfer money from one fund to another.
11998
11999 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12000 negative amount for the old (losing) fund and a positive amount for the new
12001 (gaining) fund.  In some cases there may be more than one such pair of entries
12002 in order to pull the money from different funding sources, or more specifically
12003 from different funding source credits.  For each such pair there is also an
12004 entry in acq.fund_transfer.
12005
12006 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12007 choose a funding source for the transferred money to come from.  This choice
12008 must meet two constraints, so far as possible:
12009
12010 1. The amount transferred from a given funding source must not exceed the
12011 amount allocated to the old fund by the funding source.  To that end we
12012 compare the amount being transferred to the amount allocated.
12013
12014 2. We shouldn't transfer money that has already been spent or encumbered, as
12015 defined by the funding attribution process.  We attribute expenses to the
12016 oldest funding source credits first.  In order to avoid transferring that
12017 attributed money, we reverse the priority, transferring from the newest funding
12018 source credits first.  There can be no guarantee that this approach will
12019 avoid overcommitting a fund, but no other approach can do any better.
12020
12021 In this context the age of a funding source credit is defined by the
12022 deadline_date for credits with deadline_dates, and by the effective_date for
12023 credits without deadline_dates, with the proviso that credits with deadline_dates
12024 are all considered "older" than those without.
12025
12026 ----------
12027
12028 In the signature for this function, there is one last parameter commented out,
12029 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12030 driving the main loop has an OR clause commented out, which references the
12031 funding_source_in parameter.
12032
12033 If these lines are uncommented, this function will allow the user optionally to
12034 restrict a fund transfer to a specified funding source.  If the source
12035 parameter is left NULL, then there will be no such restriction.
12036
12037 ------------------------------------------------------------------------------- */ 
12038 DECLARE
12039         same_currency      BOOLEAN;
12040         currency_ratio     NUMERIC;
12041         old_fund_currency  TEXT;
12042         old_remaining      NUMERIC;  -- in currency of old fund
12043         new_fund_currency  TEXT;
12044         new_fund_active    BOOLEAN;
12045         new_remaining      NUMERIC;  -- in currency of new fund
12046         curr_old_amt       NUMERIC;  -- in currency of old fund
12047         curr_new_amt       NUMERIC;  -- in currency of new fund
12048         source_addition    NUMERIC;  -- in currency of funding source
12049         source_deduction   NUMERIC;  -- in currency of funding source
12050         orig_allocated_amt NUMERIC;  -- in currency of funding source
12051         allocated_amt      NUMERIC;  -- in currency of fund
12052         source             RECORD;
12053 BEGIN
12054         --
12055         -- Sanity checks
12056         --
12057         IF old_fund IS NULL THEN
12058                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12059         END IF;
12060         --
12061         IF old_amount IS NULL THEN
12062                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12063         END IF;
12064         --
12065         -- The new fund and its amount must be both NULL or both not NULL.
12066         --
12067         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12068                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12069         END IF;
12070         --
12071         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12072                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12073         END IF;
12074         --
12075         IF user_id IS NULL THEN
12076                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12077         END IF;
12078         --
12079         -- Initialize the amounts to be transferred, each denominated
12080         -- in the currency of its respective fund.  They will be
12081         -- reduced on each iteration of the loop.
12082         --
12083         old_remaining := old_amount;
12084         new_remaining := new_amount;
12085         --
12086         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12087         --      old_amount, old_fund, new_amount, new_fund;
12088         --
12089         -- Get the currency types of the old and new funds.
12090         --
12091         SELECT
12092                 currency_type
12093         INTO
12094                 old_fund_currency
12095         FROM
12096                 acq.fund
12097         WHERE
12098                 id = old_fund;
12099         --
12100         IF old_fund_currency IS NULL THEN
12101                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12102         END IF;
12103         --
12104         IF new_fund IS NOT NULL THEN
12105                 SELECT
12106                         currency_type,
12107                         active
12108                 INTO
12109                         new_fund_currency,
12110                         new_fund_active
12111                 FROM
12112                         acq.fund
12113                 WHERE
12114                         id = new_fund;
12115                 --
12116                 IF new_fund_currency IS NULL THEN
12117                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12118                 ELSIF NOT new_fund_active THEN
12119                         --
12120                         -- No point in putting money into a fund from whence you can't spend it
12121                         --
12122                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12123                 END IF;
12124                 --
12125                 IF new_amount = old_amount THEN
12126                         same_currency := true;
12127                         currency_ratio := 1;
12128                 ELSE
12129                         --
12130                         -- We'll have to translate currency between funds.  We presume that
12131                         -- the calling code has already applied an appropriate exchange rate,
12132                         -- so we'll apply the same conversion to each sub-transfer.
12133                         --
12134                         same_currency := false;
12135                         currency_ratio := new_amount / old_amount;
12136                 END IF;
12137         END IF;
12138         --
12139         -- Identify the funding source(s) from which we want to transfer the money.
12140         -- The principle is that we want to transfer the newest money first, because
12141         -- we spend the oldest money first.  The priority for spending is defined
12142         -- by a sort of the view acq.ordered_funding_source_credit.
12143         --
12144         FOR source in
12145                 SELECT
12146                         ofsc.id,
12147                         ofsc.funding_source,
12148                         ofsc.amount,
12149                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12150                                 AS converted_amt,
12151                         fs.currency_type
12152                 FROM
12153                         acq.ordered_funding_source_credit AS ofsc,
12154                         acq.funding_source fs
12155                 WHERE
12156                         ofsc.funding_source = fs.id
12157                         and ofsc.funding_source IN
12158                         (
12159                                 SELECT funding_source
12160                                 FROM acq.fund_allocation
12161                                 WHERE fund = old_fund
12162                         )
12163                         -- and
12164                         -- (
12165                         --      ofsc.funding_source = funding_source_in
12166                         --      OR funding_source_in IS NULL
12167                         -- )
12168                 ORDER BY
12169                         ofsc.sort_priority desc,
12170                         ofsc.sort_date desc,
12171                         ofsc.id desc
12172         LOOP
12173                 --
12174                 -- Determine how much money the old fund got from this funding source,
12175                 -- denominated in the currency types of the source and of the fund.
12176                 -- This result may reflect transfers from previous iterations.
12177                 --
12178                 SELECT
12179                         COALESCE( sum( amount ), 0 ),
12180                         COALESCE( sum( amount )
12181                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12182                 INTO
12183                         orig_allocated_amt,     -- in currency of the source
12184                         allocated_amt           -- in currency of the old fund
12185                 FROM
12186                         acq.fund_allocation
12187                 WHERE
12188                         fund = old_fund
12189                         and funding_source = source.funding_source;
12190                 --      
12191                 -- Determine how much to transfer from this credit, in the currency
12192                 -- of the fund.   Begin with the amount remaining to be attributed:
12193                 --
12194                 curr_old_amt := old_remaining;
12195                 --
12196                 -- Can't attribute more than was allocated from the fund:
12197                 --
12198                 IF curr_old_amt > allocated_amt THEN
12199                         curr_old_amt := allocated_amt;
12200                 END IF;
12201                 --
12202                 -- Can't attribute more than the amount of the current credit:
12203                 --
12204                 IF curr_old_amt > source.converted_amt THEN
12205                         curr_old_amt := source.converted_amt;
12206                 END IF;
12207                 --
12208                 curr_old_amt := trunc( curr_old_amt, 2 );
12209                 --
12210                 old_remaining := old_remaining - curr_old_amt;
12211                 --
12212                 -- Determine the amount to be deducted, if any,
12213                 -- from the old allocation.
12214                 --
12215                 IF old_remaining > 0 THEN
12216                         --
12217                         -- In this case we're using the whole allocation, so use that
12218                         -- amount directly instead of applying a currency translation
12219                         -- and thereby inviting round-off errors.
12220                         --
12221                         source_deduction := - orig_allocated_amt;
12222                 ELSE 
12223                         source_deduction := trunc(
12224                                 ( - curr_old_amt ) *
12225                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12226                                 2 );
12227                 END IF;
12228                 --
12229                 IF source_deduction <> 0 THEN
12230                         --
12231                         -- Insert negative allocation for old fund in fund_allocation,
12232                         -- converted into the currency of the funding source
12233                         --
12234                         INSERT INTO acq.fund_allocation (
12235                                 funding_source,
12236                                 fund,
12237                                 amount,
12238                                 allocator,
12239                                 note
12240                         ) VALUES (
12241                                 source.funding_source,
12242                                 old_fund,
12243                                 source_deduction,
12244                                 user_id,
12245                                 'Transfer to fund ' || new_fund
12246                         );
12247                 END IF;
12248                 --
12249                 IF new_fund IS NOT NULL THEN
12250                         --
12251                         -- Determine how much to add to the new fund, in
12252                         -- its currency, and how much remains to be added:
12253                         --
12254                         IF same_currency THEN
12255                                 curr_new_amt := curr_old_amt;
12256                         ELSE
12257                                 IF old_remaining = 0 THEN
12258                                         --
12259                                         -- This is the last iteration, so nothing should be left
12260                                         --
12261                                         curr_new_amt := new_remaining;
12262                                         new_remaining := 0;
12263                                 ELSE
12264                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12265                                         new_remaining := new_remaining - curr_new_amt;
12266                                 END IF;
12267                         END IF;
12268                         --
12269                         -- Determine how much to add, if any,
12270                         -- to the new fund's allocation.
12271                         --
12272                         IF old_remaining > 0 THEN
12273                                 --
12274                                 -- In this case we're using the whole allocation, so use that amount
12275                                 -- amount directly instead of applying a currency translation and
12276                                 -- thereby inviting round-off errors.
12277                                 --
12278                                 source_addition := orig_allocated_amt;
12279                         ELSIF source.currency_type = old_fund_currency THEN
12280                                 --
12281                                 -- In this case we don't need a round trip currency translation,
12282                                 -- thereby inviting round-off errors:
12283                                 --
12284                                 source_addition := curr_old_amt;
12285                         ELSE 
12286                                 source_addition := trunc(
12287                                         curr_new_amt *
12288                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12289                                         2 );
12290                         END IF;
12291                         --
12292                         IF source_addition <> 0 THEN
12293                                 --
12294                                 -- Insert positive allocation for new fund in fund_allocation,
12295                                 -- converted to the currency of the founding source
12296                                 --
12297                                 INSERT INTO acq.fund_allocation (
12298                                         funding_source,
12299                                         fund,
12300                                         amount,
12301                                         allocator,
12302                                         note
12303                                 ) VALUES (
12304                                         source.funding_source,
12305                                         new_fund,
12306                                         source_addition,
12307                                         user_id,
12308                                         'Transfer from fund ' || old_fund
12309                                 );
12310                         END IF;
12311                 END IF;
12312                 --
12313                 IF trunc( curr_old_amt, 2 ) <> 0
12314                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12315                         --
12316                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12317                         --
12318                         INSERT INTO acq.fund_transfer (
12319                                 src_fund,
12320                                 src_amount,
12321                                 dest_fund,
12322                                 dest_amount,
12323                                 transfer_user,
12324                                 note,
12325                                 funding_source_credit
12326                         ) VALUES (
12327                                 old_fund,
12328                                 trunc( curr_old_amt, 2 ),
12329                                 new_fund,
12330                                 trunc( curr_new_amt, 2 ),
12331                                 user_id,
12332                                 xfer_note,
12333                                 source.id
12334                         );
12335                 END IF;
12336                 --
12337                 if old_remaining <= 0 THEN
12338                         EXIT;                   -- Nothing more to be transferred
12339                 END IF;
12340         END LOOP;
12341 END;
12342 $$ LANGUAGE plpgsql;
12343
12344 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
12345         old_year INTEGER,
12346         user_id INTEGER,
12347         org_unit_id INTEGER
12348 ) RETURNS VOID AS $$
12349 DECLARE
12350 --
12351 new_id      INT;
12352 old_fund    RECORD;
12353 org_found   BOOLEAN;
12354 --
12355 BEGIN
12356         --
12357         -- Sanity checks
12358         --
12359         IF old_year IS NULL THEN
12360                 RAISE EXCEPTION 'Input year argument is NULL';
12361         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12362                 RAISE EXCEPTION 'Input year is out of range';
12363         END IF;
12364         --
12365         IF user_id IS NULL THEN
12366                 RAISE EXCEPTION 'Input user id argument is NULL';
12367         END IF;
12368         --
12369         IF org_unit_id IS NULL THEN
12370                 RAISE EXCEPTION 'Org unit id argument is NULL';
12371         ELSE
12372                 SELECT TRUE INTO org_found
12373                 FROM actor.org_unit
12374                 WHERE id = org_unit_id;
12375                 --
12376                 IF org_found IS NULL THEN
12377                         RAISE EXCEPTION 'Org unit id is invalid';
12378                 END IF;
12379         END IF;
12380         --
12381         -- Loop over the applicable funds
12382         --
12383         FOR old_fund in SELECT * FROM acq.fund
12384         WHERE
12385                 year = old_year
12386                 AND propagate
12387                 AND org = org_unit_id
12388         LOOP
12389                 BEGIN
12390                         INSERT INTO acq.fund (
12391                                 org,
12392                                 name,
12393                                 year,
12394                                 currency_type,
12395                                 code,
12396                                 rollover,
12397                                 propagate,
12398                                 balance_warning_percent,
12399                                 balance_stop_percent
12400                         ) VALUES (
12401                                 old_fund.org,
12402                                 old_fund.name,
12403                                 old_year + 1,
12404                                 old_fund.currency_type,
12405                                 old_fund.code,
12406                                 old_fund.rollover,
12407                                 true,
12408                                 old_fund.balance_warning_percent,
12409                                 old_fund.balance_stop_percent
12410                         )
12411                         RETURNING id INTO new_id;
12412                 EXCEPTION
12413                         WHEN unique_violation THEN
12414                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12415                                 CONTINUE;
12416                 END;
12417                 --RAISE NOTICE 'Propagating fund % to fund %',
12418                 --      old_fund.code, new_id;
12419         END LOOP;
12420 END;
12421 $$ LANGUAGE plpgsql;
12422
12423 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12424         old_year INTEGER,
12425         user_id INTEGER,
12426         org_unit_id INTEGER
12427 ) RETURNS VOID AS $$
12428 DECLARE
12429 --
12430 new_id      INT;
12431 old_fund    RECORD;
12432 org_found   BOOLEAN;
12433 --
12434 BEGIN
12435         --
12436         -- Sanity checks
12437         --
12438         IF old_year IS NULL THEN
12439                 RAISE EXCEPTION 'Input year argument is NULL';
12440         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12441                 RAISE EXCEPTION 'Input year is out of range';
12442         END IF;
12443         --
12444         IF user_id IS NULL THEN
12445                 RAISE EXCEPTION 'Input user id argument is NULL';
12446         END IF;
12447         --
12448         IF org_unit_id IS NULL THEN
12449                 RAISE EXCEPTION 'Org unit id argument is NULL';
12450         ELSE
12451                 SELECT TRUE INTO org_found
12452                 FROM actor.org_unit
12453                 WHERE id = org_unit_id;
12454                 --
12455                 IF org_found IS NULL THEN
12456                         RAISE EXCEPTION 'Org unit id is invalid';
12457                 END IF;
12458         END IF;
12459         --
12460         -- Loop over the applicable funds
12461         --
12462         FOR old_fund in SELECT * FROM acq.fund
12463         WHERE
12464                 year = old_year
12465                 AND propagate
12466                 AND org in (
12467                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12468                 )
12469         LOOP
12470                 BEGIN
12471                         INSERT INTO acq.fund (
12472                                 org,
12473                                 name,
12474                                 year,
12475                                 currency_type,
12476                                 code,
12477                                 rollover,
12478                                 propagate,
12479                                 balance_warning_percent,
12480                                 balance_stop_percent
12481                         ) VALUES (
12482                                 old_fund.org,
12483                                 old_fund.name,
12484                                 old_year + 1,
12485                                 old_fund.currency_type,
12486                                 old_fund.code,
12487                                 old_fund.rollover,
12488                                 true,
12489                                 old_fund.balance_warning_percent,
12490                                 old_fund.balance_stop_percent
12491                         )
12492                         RETURNING id INTO new_id;
12493                 EXCEPTION
12494                         WHEN unique_violation THEN
12495                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12496                                 CONTINUE;
12497                 END;
12498                 --RAISE NOTICE 'Propagating fund % to fund %',
12499                 --      old_fund.code, new_id;
12500         END LOOP;
12501 END;
12502 $$ LANGUAGE plpgsql;
12503
12504 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12505         old_year INTEGER,
12506         user_id INTEGER,
12507         org_unit_id INTEGER
12508 ) RETURNS VOID AS $$
12509 DECLARE
12510 --
12511 new_fund    INT;
12512 new_year    INT := old_year + 1;
12513 org_found   BOOL;
12514 xfer_amount NUMERIC;
12515 roll_fund   RECORD;
12516 deb         RECORD;
12517 detail      RECORD;
12518 --
12519 BEGIN
12520         --
12521         -- Sanity checks
12522         --
12523         IF old_year IS NULL THEN
12524                 RAISE EXCEPTION 'Input year argument is NULL';
12525     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12526         RAISE EXCEPTION 'Input year is out of range';
12527         END IF;
12528         --
12529         IF user_id IS NULL THEN
12530                 RAISE EXCEPTION 'Input user id argument is NULL';
12531         END IF;
12532         --
12533         IF org_unit_id IS NULL THEN
12534                 RAISE EXCEPTION 'Org unit id argument is NULL';
12535         ELSE
12536                 --
12537                 -- Validate the org unit
12538                 --
12539                 SELECT TRUE
12540                 INTO org_found
12541                 FROM actor.org_unit
12542                 WHERE id = org_unit_id;
12543                 --
12544                 IF org_found IS NULL THEN
12545                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12546                 END IF;
12547         END IF;
12548         --
12549         -- Loop over the propagable funds to identify the details
12550         -- from the old fund plus the id of the new one, if it exists.
12551         --
12552         FOR roll_fund in
12553         SELECT
12554             oldf.id AS old_fund,
12555             oldf.org,
12556             oldf.name,
12557             oldf.currency_type,
12558             oldf.code,
12559                 oldf.rollover,
12560             newf.id AS new_fund_id
12561         FROM
12562         acq.fund AS oldf
12563         LEFT JOIN acq.fund AS newf
12564                 ON ( oldf.code = newf.code )
12565         WHERE
12566                     oldf.org = org_unit_id
12567                 and oldf.year = old_year
12568                 and oldf.propagate
12569         and newf.year = new_year
12570         LOOP
12571                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12572                 --
12573                 IF roll_fund.new_fund_id IS NULL THEN
12574                         --
12575                         -- The old fund hasn't been propagated yet.  Propagate it now.
12576                         --
12577                         INSERT INTO acq.fund (
12578                                 org,
12579                                 name,
12580                                 year,
12581                                 currency_type,
12582                                 code,
12583                                 rollover,
12584                                 propagate,
12585                                 balance_warning_percent,
12586                                 balance_stop_percent
12587                         ) VALUES (
12588                                 roll_fund.org,
12589                                 roll_fund.name,
12590                                 new_year,
12591                                 roll_fund.currency_type,
12592                                 roll_fund.code,
12593                                 true,
12594                                 true,
12595                                 roll_fund.balance_warning_percent,
12596                                 roll_fund.balance_stop_percent
12597                         )
12598                         RETURNING id INTO new_fund;
12599                 ELSE
12600                         new_fund = roll_fund.new_fund_id;
12601                 END IF;
12602                 --
12603                 -- Determine the amount to transfer
12604                 --
12605                 SELECT amount
12606                 INTO xfer_amount
12607                 FROM acq.fund_spent_balance
12608                 WHERE fund = roll_fund.old_fund;
12609                 --
12610                 IF xfer_amount <> 0 THEN
12611                         IF roll_fund.rollover THEN
12612                                 --
12613                                 -- Transfer balance from old fund to new
12614                                 --
12615                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12616                                 --
12617                                 PERFORM acq.transfer_fund(
12618                                         roll_fund.old_fund,
12619                                         xfer_amount,
12620                                         new_fund,
12621                                         xfer_amount,
12622                                         user_id,
12623                                         'Rollover'
12624                                 );
12625                         ELSE
12626                                 --
12627                                 -- Transfer balance from old fund to the void
12628                                 --
12629                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12630                                 --
12631                                 PERFORM acq.transfer_fund(
12632                                         roll_fund.old_fund,
12633                                         xfer_amount,
12634                                         NULL,
12635                                         NULL,
12636                                         user_id,
12637                                         'Rollover'
12638                                 );
12639                         END IF;
12640                 END IF;
12641                 --
12642                 IF roll_fund.rollover THEN
12643                         --
12644                         -- Move any lineitems from the old fund to the new one
12645                         -- where the associated debit is an encumbrance.
12646                         --
12647                         -- Any other tables tying expenditure details to funds should
12648                         -- receive similar treatment.  At this writing there are none.
12649                         --
12650                         UPDATE acq.lineitem_detail
12651                         SET fund = new_fund
12652                         WHERE
12653                         fund = roll_fund.old_fund -- this condition may be redundant
12654                         AND fund_debit in
12655                         (
12656                                 SELECT id
12657                                 FROM acq.fund_debit
12658                                 WHERE
12659                                 fund = roll_fund.old_fund
12660                                 AND encumbrance
12661                         );
12662                         --
12663                         -- Move encumbrance debits from the old fund to the new fund
12664                         --
12665                         UPDATE acq.fund_debit
12666                         SET fund = new_fund
12667                         wHERE
12668                                 fund = roll_fund.old_fund
12669                                 AND encumbrance;
12670                 END IF;
12671                 --
12672                 -- Mark old fund as inactive, now that we've closed it
12673                 --
12674                 UPDATE acq.fund
12675                 SET active = FALSE
12676                 WHERE id = roll_fund.old_fund;
12677         END LOOP;
12678 END;
12679 $$ LANGUAGE plpgsql;
12680
12681 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12682         old_year INTEGER,
12683         user_id INTEGER,
12684         org_unit_id INTEGER
12685 ) RETURNS VOID AS $$
12686 DECLARE
12687 --
12688 new_fund    INT;
12689 new_year    INT := old_year + 1;
12690 org_found   BOOL;
12691 xfer_amount NUMERIC;
12692 roll_fund   RECORD;
12693 deb         RECORD;
12694 detail      RECORD;
12695 --
12696 BEGIN
12697         --
12698         -- Sanity checks
12699         --
12700         IF old_year IS NULL THEN
12701                 RAISE EXCEPTION 'Input year argument is NULL';
12702     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12703         RAISE EXCEPTION 'Input year is out of range';
12704         END IF;
12705         --
12706         IF user_id IS NULL THEN
12707                 RAISE EXCEPTION 'Input user id argument is NULL';
12708         END IF;
12709         --
12710         IF org_unit_id IS NULL THEN
12711                 RAISE EXCEPTION 'Org unit id argument is NULL';
12712         ELSE
12713                 --
12714                 -- Validate the org unit
12715                 --
12716                 SELECT TRUE
12717                 INTO org_found
12718                 FROM actor.org_unit
12719                 WHERE id = org_unit_id;
12720                 --
12721                 IF org_found IS NULL THEN
12722                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12723                 END IF;
12724         END IF;
12725         --
12726         -- Loop over the propagable funds to identify the details
12727         -- from the old fund plus the id of the new one, if it exists.
12728         --
12729         FOR roll_fund in
12730         SELECT
12731             oldf.id AS old_fund,
12732             oldf.org,
12733             oldf.name,
12734             oldf.currency_type,
12735             oldf.code,
12736                 oldf.rollover,
12737             newf.id AS new_fund_id
12738         FROM
12739         acq.fund AS oldf
12740         LEFT JOIN acq.fund AS newf
12741                 ON ( oldf.code = newf.code )
12742         WHERE
12743                     oldf.year = old_year
12744                 AND oldf.propagate
12745         AND newf.year = new_year
12746                 AND oldf.org in (
12747                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12748                 )
12749         LOOP
12750                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12751                 --
12752                 IF roll_fund.new_fund_id IS NULL THEN
12753                         --
12754                         -- The old fund hasn't been propagated yet.  Propagate it now.
12755                         --
12756                         INSERT INTO acq.fund (
12757                                 org,
12758                                 name,
12759                                 year,
12760                                 currency_type,
12761                                 code,
12762                                 rollover,
12763                                 propagate,
12764                                 balance_warning_percent,
12765                                 balance_stop_percent
12766                         ) VALUES (
12767                                 roll_fund.org,
12768                                 roll_fund.name,
12769                                 new_year,
12770                                 roll_fund.currency_type,
12771                                 roll_fund.code,
12772                                 true,
12773                                 true,
12774                                 roll_fund.balance_warning_percent,
12775                                 roll_fund.balance_stop_percent
12776                         )
12777                         RETURNING id INTO new_fund;
12778                 ELSE
12779                         new_fund = roll_fund.new_fund_id;
12780                 END IF;
12781                 --
12782                 -- Determine the amount to transfer
12783                 --
12784                 SELECT amount
12785                 INTO xfer_amount
12786                 FROM acq.fund_spent_balance
12787                 WHERE fund = roll_fund.old_fund;
12788                 --
12789                 IF xfer_amount <> 0 THEN
12790                         IF roll_fund.rollover THEN
12791                                 --
12792                                 -- Transfer balance from old fund to new
12793                                 --
12794                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12795                                 --
12796                                 PERFORM acq.transfer_fund(
12797                                         roll_fund.old_fund,
12798                                         xfer_amount,
12799                                         new_fund,
12800                                         xfer_amount,
12801                                         user_id,
12802                                         'Rollover'
12803                                 );
12804                         ELSE
12805                                 --
12806                                 -- Transfer balance from old fund to the void
12807                                 --
12808                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12809                                 --
12810                                 PERFORM acq.transfer_fund(
12811                                         roll_fund.old_fund,
12812                                         xfer_amount,
12813                                         NULL,
12814                                         NULL,
12815                                         user_id,
12816                                         'Rollover'
12817                                 );
12818                         END IF;
12819                 END IF;
12820                 --
12821                 IF roll_fund.rollover THEN
12822                         --
12823                         -- Move any lineitems from the old fund to the new one
12824                         -- where the associated debit is an encumbrance.
12825                         --
12826                         -- Any other tables tying expenditure details to funds should
12827                         -- receive similar treatment.  At this writing there are none.
12828                         --
12829                         UPDATE acq.lineitem_detail
12830                         SET fund = new_fund
12831                         WHERE
12832                         fund = roll_fund.old_fund -- this condition may be redundant
12833                         AND fund_debit in
12834                         (
12835                                 SELECT id
12836                                 FROM acq.fund_debit
12837                                 WHERE
12838                                 fund = roll_fund.old_fund
12839                                 AND encumbrance
12840                         );
12841                         --
12842                         -- Move encumbrance debits from the old fund to the new fund
12843                         --
12844                         UPDATE acq.fund_debit
12845                         SET fund = new_fund
12846                         wHERE
12847                                 fund = roll_fund.old_fund
12848                                 AND encumbrance;
12849                 END IF;
12850                 --
12851                 -- Mark old fund as inactive, now that we've closed it
12852                 --
12853                 UPDATE acq.fund
12854                 SET active = FALSE
12855                 WHERE id = roll_fund.old_fund;
12856         END LOOP;
12857 END;
12858 $$ LANGUAGE plpgsql;
12859
12860 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
12861     SELECT regexp_replace($1, ',', '', 'g');
12862 $$ LANGUAGE SQL STRICT IMMUTABLE;
12863
12864 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
12865     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
12866 $$ LANGUAGE SQL STRICT IMMUTABLE;
12867
12868 CREATE TABLE acq.distribution_formula_application (
12869     id BIGSERIAL PRIMARY KEY,
12870     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
12871     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
12872     formula INT NOT NULL
12873         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
12874     lineitem INT NOT NULL
12875         REFERENCES acq.lineitem( id )
12876                 ON DELETE CASCADE
12877                 DEFERRABLE INITIALLY DEFERRED
12878 );
12879
12880 CREATE INDEX acqdfa_df_idx
12881     ON acq.distribution_formula_application(formula);
12882 CREATE INDEX acqdfa_li_idx
12883     ON acq.distribution_formula_application(lineitem);
12884 CREATE INDEX acqdfa_creator_idx
12885     ON acq.distribution_formula_application(creator);
12886
12887 CREATE TABLE acq.user_request_type (
12888     id      SERIAL  PRIMARY KEY,
12889     label   TEXT    NOT NULL UNIQUE -- i18n-ize
12890 );
12891
12892 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
12893 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
12894 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
12895 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
12896 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
12897
12898 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
12899
12900 CREATE TABLE acq.cancel_reason (
12901         id            SERIAL            PRIMARY KEY,
12902         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
12903                                         DEFERRABLE INITIALLY DEFERRED,
12904         label         TEXT              NOT NULL,
12905         description   TEXT              NOT NULL,
12906         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
12907         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
12908 );
12909
12910 -- Reserve ids 1-999 for stock reasons
12911 -- Reserve ids 1000-1999 for EDI reasons
12912 -- 2000+ are available for staff to create
12913
12914 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
12915
12916 CREATE TABLE acq.user_request (
12917     id                  SERIAL  PRIMARY KEY,
12918     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
12919     hold                BOOL    NOT NULL DEFAULT TRUE,
12920
12921     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
12922     holdable_formats    TEXT,           -- nullable, for use in hold creation
12923     phone_notify        TEXT,
12924     email_notify        BOOL    NOT NULL DEFAULT TRUE,
12925     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
12926     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
12927     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
12928     need_before         TIMESTAMPTZ,    -- don't create holds after this
12929     max_fee             TEXT,
12930
12931     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
12932     isxn                TEXT,
12933     title               TEXT,
12934     volume              TEXT,
12935     author              TEXT,
12936     article_title       TEXT,
12937     article_pages       TEXT,
12938     publisher           TEXT,
12939     location            TEXT,
12940     pubdate             TEXT,
12941     mentioned           TEXT,
12942     other_info          TEXT,
12943         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
12944                                              DEFERRABLE INITIALLY DEFERRED
12945 );
12946
12947 CREATE TABLE acq.lineitem_alert_text (
12948         id               SERIAL         PRIMARY KEY,
12949         code             TEXT           NOT NULL,
12950         description      TEXT,
12951         owning_lib       INT            NOT NULL
12952                                         REFERENCES actor.org_unit(id)
12953                                         DEFERRABLE INITIALLY DEFERRED,
12954         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
12955 );
12956
12957 ALTER TABLE acq.lineitem_note
12958         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
12959                                          DEFERRABLE INITIALLY DEFERRED;
12960
12961 -- add ON DELETE CASCADE clause
12962
12963 ALTER TABLE acq.lineitem_note
12964         DROP CONSTRAINT lineitem_note_lineitem_fkey;
12965
12966 ALTER TABLE acq.lineitem_note
12967         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
12968                 ON DELETE CASCADE
12969                 DEFERRABLE INITIALLY DEFERRED;
12970
12971 ALTER TABLE acq.lineitem_note
12972         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
12973
12974 CREATE TABLE acq.invoice_method (
12975     code    TEXT    PRIMARY KEY,
12976     name    TEXT    NOT NULL -- i18n-ize
12977 );
12978 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
12979 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
12980
12981 CREATE TABLE acq.invoice_payment_method (
12982         code      TEXT     PRIMARY KEY,
12983         name      TEXT     NOT NULL
12984 );
12985
12986 CREATE TABLE acq.invoice (
12987     id             SERIAL      PRIMARY KEY,
12988     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
12989     provider       INT         NOT NULL REFERENCES acq.provider (id),
12990     shipper        INT         NOT NULL REFERENCES acq.provider (id),
12991     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
12992     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
12993     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
12994     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
12995         payment_auth   TEXT,
12996         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
12997                                    DEFERRABLE INITIALLY DEFERRED,
12998         note           TEXT,
12999     complete       BOOL        NOT NULL DEFAULT FALSE,
13000     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13001 );
13002
13003 CREATE TABLE acq.invoice_entry (
13004     id              SERIAL      PRIMARY KEY,
13005     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13006     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13007     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13008     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13009     phys_item_count INT, -- and how many did staff count
13010     note            TEXT,
13011     billed_per_item BOOL,
13012     cost_billed     NUMERIC(8,2),
13013     actual_cost     NUMERIC(8,2),
13014         amount_paid     NUMERIC (8,2)
13015 );
13016
13017 CREATE TABLE acq.invoice_item_type (
13018     code    TEXT    PRIMARY KEY,
13019     name    TEXT    NOT NULL, -- i18n-ize
13020         prorate BOOL    NOT NULL DEFAULT FALSE
13021 );
13022
13023 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13024 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13025 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13026 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13027 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13028 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13029
13030 CREATE TABLE acq.po_item (
13031         id              SERIAL      PRIMARY KEY,
13032         purchase_order  INT         REFERENCES acq.purchase_order (id)
13033                                     ON UPDATE CASCADE ON DELETE SET NULL
13034                                     DEFERRABLE INITIALLY DEFERRED,
13035         fund_debit      INT         REFERENCES acq.fund_debit (id)
13036                                     DEFERRABLE INITIALLY DEFERRED,
13037         inv_item_type   TEXT        NOT NULL
13038                                     REFERENCES acq.invoice_item_type (code)
13039                                     DEFERRABLE INITIALLY DEFERRED,
13040         title           TEXT,
13041         author          TEXT,
13042         note            TEXT,
13043         estimated_cost  NUMERIC(8,2),
13044         fund            INT         REFERENCES acq.fund (id)
13045                                     DEFERRABLE INITIALLY DEFERRED,
13046         target          BIGINT
13047 );
13048
13049 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13050     id              SERIAL      PRIMARY KEY,
13051     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13052     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13053     fund_debit      INT         REFERENCES acq.fund_debit (id),
13054     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13055     title           TEXT,
13056     author          TEXT,
13057     note            TEXT,
13058     cost_billed     NUMERIC(8,2),
13059     actual_cost     NUMERIC(8,2),
13060     fund            INT         REFERENCES acq.fund (id)
13061                                 DEFERRABLE INITIALLY DEFERRED,
13062     amount_paid     NUMERIC (8,2),
13063     po_item         INT         REFERENCES acq.po_item (id)
13064                                 DEFERRABLE INITIALLY DEFERRED,
13065     target          BIGINT
13066 );
13067
13068 CREATE TABLE acq.edi_message (
13069     id               SERIAL          PRIMARY KEY,
13070     account          INTEGER         REFERENCES acq.edi_account(id)
13071                                      DEFERRABLE INITIALLY DEFERRED,
13072     remote_file      TEXT,
13073     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13074     translate_time   TIMESTAMPTZ,
13075     process_time     TIMESTAMPTZ,
13076     error_time       TIMESTAMPTZ,
13077     status           TEXT            NOT NULL DEFAULT 'new'
13078                                      CONSTRAINT status_value CHECK
13079                                      ( status IN (
13080                                         'new',          -- needs to be translated
13081                                         'translated',   -- needs to be processed
13082                                         'trans_error',  -- error in translation step
13083                                         'processed',    -- needs to have remote_file deleted
13084                                         'proc_error',   -- error in processing step
13085                                         'delete_error', -- error in deletion
13086                                         'retry',        -- need to retry
13087                                         'complete'      -- done
13088                                      )),
13089     edi              TEXT,
13090     jedi             TEXT,
13091     error            TEXT,
13092     purchase_order   INT             REFERENCES acq.purchase_order
13093                                      DEFERRABLE INITIALLY DEFERRED,
13094     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13095                                      CHECK ( message_type IN (
13096                                         'ORDERS',
13097                                         'ORDRSP',
13098                                         'INVOIC',
13099                                         'OSTENQ',
13100                                         'OSTRPT'
13101                                      ))
13102 );
13103
13104 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13105
13106 ALTER TABLE acq.provider_address
13107         ADD COLUMN fax_phone TEXT;
13108
13109 ALTER TABLE acq.provider_contact_address
13110         ADD COLUMN fax_phone TEXT;
13111
13112 CREATE TABLE acq.provider_note (
13113     id      SERIAL              PRIMARY KEY,
13114     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13115     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13116     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13117     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13118     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13119     value       TEXT            NOT NULL
13120 );
13121 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13122 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13123 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13124
13125 -- For each fund: the total allocation from all sources, in the
13126 -- currency of the fund (or 0 if there are no allocations)
13127
13128 CREATE VIEW acq.all_fund_allocation_total AS
13129 SELECT
13130     f.id AS fund,
13131     COALESCE( SUM( a.amount * acq.exchange_ratio(
13132         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13133     AS amount
13134 FROM
13135     acq.fund f
13136         LEFT JOIN acq.fund_allocation a
13137             ON a.fund = f.id
13138         LEFT JOIN acq.funding_source s
13139             ON a.funding_source = s.id
13140 GROUP BY
13141     f.id;
13142
13143 -- For every fund: the total encumbrances (or 0 if none),
13144 -- in the currency of the fund.
13145
13146 CREATE VIEW acq.all_fund_encumbrance_total AS
13147 SELECT
13148         f.id AS fund,
13149         COALESCE( encumb.amount, 0 ) AS amount
13150 FROM
13151         acq.fund AS f
13152                 LEFT JOIN (
13153                         SELECT
13154                                 fund,
13155                                 sum( amount ) AS amount
13156                         FROM
13157                                 acq.fund_debit
13158                         WHERE
13159                                 encumbrance
13160                         GROUP BY fund
13161                 ) AS encumb
13162                         ON f.id = encumb.fund;
13163
13164 -- For every fund: the total spent (or 0 if none),
13165 -- in the currency of the fund.
13166
13167 CREATE VIEW acq.all_fund_spent_total AS
13168 SELECT
13169     f.id AS fund,
13170     COALESCE( spent.amount, 0 ) AS amount
13171 FROM
13172     acq.fund AS f
13173         LEFT JOIN (
13174             SELECT
13175                 fund,
13176                 sum( amount ) AS amount
13177             FROM
13178                 acq.fund_debit
13179             WHERE
13180                 NOT encumbrance
13181             GROUP BY fund
13182         ) AS spent
13183             ON f.id = spent.fund;
13184
13185 -- For each fund: the amount not yet spent, in the currency
13186 -- of the fund.  May include encumbrances.
13187
13188 CREATE VIEW acq.all_fund_spent_balance AS
13189 SELECT
13190         c.fund,
13191         c.amount - d.amount AS amount
13192 FROM acq.all_fund_allocation_total c
13193     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13194
13195 -- For each fund: the amount neither spent nor encumbered,
13196 -- in the currency of the fund
13197
13198 CREATE VIEW acq.all_fund_combined_balance AS
13199 SELECT
13200      a.fund,
13201      a.amount - COALESCE( c.amount, 0 ) AS amount
13202 FROM
13203      acq.all_fund_allocation_total a
13204         LEFT OUTER JOIN (
13205             SELECT
13206                 fund,
13207                 SUM( amount ) AS amount
13208             FROM
13209                 acq.fund_debit
13210             GROUP BY
13211                 fund
13212         ) AS c USING ( fund );
13213
13214 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 $$
13215 DECLARE
13216         suffix TEXT;
13217         bucket_row RECORD;
13218         picklist_row RECORD;
13219         queue_row RECORD;
13220         folder_row RECORD;
13221 BEGIN
13222
13223     -- do some initial cleanup 
13224     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13225     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13226     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13227
13228     -- actor.*
13229     IF del_cards THEN
13230         DELETE FROM actor.card where usr = src_usr;
13231     ELSE
13232         IF deactivate_cards THEN
13233             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13234         END IF;
13235         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13236     END IF;
13237
13238
13239     IF del_addrs THEN
13240         DELETE FROM actor.usr_address WHERE usr = src_usr;
13241     ELSE
13242         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13243     END IF;
13244
13245     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13246     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13247     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13248     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13249     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13250
13251     -- permission.*
13252     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13253     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13254     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13255     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13256
13257
13258     -- container.*
13259         
13260         -- For each *_bucket table: transfer every bucket belonging to src_usr
13261         -- into the custody of dest_usr.
13262         --
13263         -- In order to avoid colliding with an existing bucket owned by
13264         -- the destination user, append the source user's id (in parenthesese)
13265         -- to the name.  If you still get a collision, add successive
13266         -- spaces to the name and keep trying until you succeed.
13267         --
13268         FOR bucket_row in
13269                 SELECT id, name
13270                 FROM   container.biblio_record_entry_bucket
13271                 WHERE  owner = src_usr
13272         LOOP
13273                 suffix := ' (' || src_usr || ')';
13274                 LOOP
13275                         BEGIN
13276                                 UPDATE  container.biblio_record_entry_bucket
13277                                 SET     owner = dest_usr, name = name || suffix
13278                                 WHERE   id = bucket_row.id;
13279                         EXCEPTION WHEN unique_violation THEN
13280                                 suffix := suffix || ' ';
13281                                 CONTINUE;
13282                         END;
13283                         EXIT;
13284                 END LOOP;
13285         END LOOP;
13286
13287         FOR bucket_row in
13288                 SELECT id, name
13289                 FROM   container.call_number_bucket
13290                 WHERE  owner = src_usr
13291         LOOP
13292                 suffix := ' (' || src_usr || ')';
13293                 LOOP
13294                         BEGIN
13295                                 UPDATE  container.call_number_bucket
13296                                 SET     owner = dest_usr, name = name || suffix
13297                                 WHERE   id = bucket_row.id;
13298                         EXCEPTION WHEN unique_violation THEN
13299                                 suffix := suffix || ' ';
13300                                 CONTINUE;
13301                         END;
13302                         EXIT;
13303                 END LOOP;
13304         END LOOP;
13305
13306         FOR bucket_row in
13307                 SELECT id, name
13308                 FROM   container.copy_bucket
13309                 WHERE  owner = src_usr
13310         LOOP
13311                 suffix := ' (' || src_usr || ')';
13312                 LOOP
13313                         BEGIN
13314                                 UPDATE  container.copy_bucket
13315                                 SET     owner = dest_usr, name = name || suffix
13316                                 WHERE   id = bucket_row.id;
13317                         EXCEPTION WHEN unique_violation THEN
13318                                 suffix := suffix || ' ';
13319                                 CONTINUE;
13320                         END;
13321                         EXIT;
13322                 END LOOP;
13323         END LOOP;
13324
13325         FOR bucket_row in
13326                 SELECT id, name
13327                 FROM   container.user_bucket
13328                 WHERE  owner = src_usr
13329         LOOP
13330                 suffix := ' (' || src_usr || ')';
13331                 LOOP
13332                         BEGIN
13333                                 UPDATE  container.user_bucket
13334                                 SET     owner = dest_usr, name = name || suffix
13335                                 WHERE   id = bucket_row.id;
13336                         EXCEPTION WHEN unique_violation THEN
13337                                 suffix := suffix || ' ';
13338                                 CONTINUE;
13339                         END;
13340                         EXIT;
13341                 END LOOP;
13342         END LOOP;
13343
13344         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13345
13346     -- vandelay.*
13347         -- transfer queues the same way we transfer buckets (see above)
13348         FOR queue_row in
13349                 SELECT id, name
13350                 FROM   vandelay.queue
13351                 WHERE  owner = src_usr
13352         LOOP
13353                 suffix := ' (' || src_usr || ')';
13354                 LOOP
13355                         BEGIN
13356                                 UPDATE  vandelay.queue
13357                                 SET     owner = dest_usr, name = name || suffix
13358                                 WHERE   id = queue_row.id;
13359                         EXCEPTION WHEN unique_violation THEN
13360                                 suffix := suffix || ' ';
13361                                 CONTINUE;
13362                         END;
13363                         EXIT;
13364                 END LOOP;
13365         END LOOP;
13366
13367     -- money.*
13368     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13369     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13370     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13371     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13372     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13373
13374     -- action.*
13375     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13376     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13377     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13378
13379     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13380     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13381     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13382     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13383
13384     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13385     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13386     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13387     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13388     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13389
13390     -- acq.*
13391     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13392         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13393
13394         -- transfer picklists the same way we transfer buckets (see above)
13395         FOR picklist_row in
13396                 SELECT id, name
13397                 FROM   acq.picklist
13398                 WHERE  owner = src_usr
13399         LOOP
13400                 suffix := ' (' || src_usr || ')';
13401                 LOOP
13402                         BEGIN
13403                                 UPDATE  acq.picklist
13404                                 SET     owner = dest_usr, name = name || suffix
13405                                 WHERE   id = picklist_row.id;
13406                         EXCEPTION WHEN unique_violation THEN
13407                                 suffix := suffix || ' ';
13408                                 CONTINUE;
13409                         END;
13410                         EXIT;
13411                 END LOOP;
13412         END LOOP;
13413
13414     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13415     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13416     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13417     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13418     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13419     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13420     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13421     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13422
13423     -- asset.*
13424     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13425     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13426     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13427     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13428     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13429     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13430
13431     -- serial.*
13432     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13433     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13434
13435     -- reporter.*
13436     -- It's not uncommon to define the reporter schema in a replica 
13437     -- DB only, so don't assume these tables exist in the write DB.
13438     BEGIN
13439         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13440     EXCEPTION WHEN undefined_table THEN
13441         -- do nothing
13442     END;
13443     BEGIN
13444         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13445     EXCEPTION WHEN undefined_table THEN
13446         -- do nothing
13447     END;
13448     BEGIN
13449         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13450     EXCEPTION WHEN undefined_table THEN
13451         -- do nothing
13452     END;
13453     BEGIN
13454                 -- transfer folders the same way we transfer buckets (see above)
13455                 FOR folder_row in
13456                         SELECT id, name
13457                         FROM   reporter.template_folder
13458                         WHERE  owner = src_usr
13459                 LOOP
13460                         suffix := ' (' || src_usr || ')';
13461                         LOOP
13462                                 BEGIN
13463                                         UPDATE  reporter.template_folder
13464                                         SET     owner = dest_usr, name = name || suffix
13465                                         WHERE   id = folder_row.id;
13466                                 EXCEPTION WHEN unique_violation THEN
13467                                         suffix := suffix || ' ';
13468                                         CONTINUE;
13469                                 END;
13470                                 EXIT;
13471                         END LOOP;
13472                 END LOOP;
13473     EXCEPTION WHEN undefined_table THEN
13474         -- do nothing
13475     END;
13476     BEGIN
13477                 -- transfer folders the same way we transfer buckets (see above)
13478                 FOR folder_row in
13479                         SELECT id, name
13480                         FROM   reporter.report_folder
13481                         WHERE  owner = src_usr
13482                 LOOP
13483                         suffix := ' (' || src_usr || ')';
13484                         LOOP
13485                                 BEGIN
13486                                         UPDATE  reporter.report_folder
13487                                         SET     owner = dest_usr, name = name || suffix
13488                                         WHERE   id = folder_row.id;
13489                                 EXCEPTION WHEN unique_violation THEN
13490                                         suffix := suffix || ' ';
13491                                         CONTINUE;
13492                                 END;
13493                                 EXIT;
13494                         END LOOP;
13495                 END LOOP;
13496     EXCEPTION WHEN undefined_table THEN
13497         -- do nothing
13498     END;
13499     BEGIN
13500                 -- transfer folders the same way we transfer buckets (see above)
13501                 FOR folder_row in
13502                         SELECT id, name
13503                         FROM   reporter.output_folder
13504                         WHERE  owner = src_usr
13505                 LOOP
13506                         suffix := ' (' || src_usr || ')';
13507                         LOOP
13508                                 BEGIN
13509                                         UPDATE  reporter.output_folder
13510                                         SET     owner = dest_usr, name = name || suffix
13511                                         WHERE   id = folder_row.id;
13512                                 EXCEPTION WHEN unique_violation THEN
13513                                         suffix := suffix || ' ';
13514                                         CONTINUE;
13515                                 END;
13516                                 EXIT;
13517                         END LOOP;
13518                 END LOOP;
13519     EXCEPTION WHEN undefined_table THEN
13520         -- do nothing
13521     END;
13522
13523     -- Finally, delete the source user
13524     DELETE FROM actor.usr WHERE id = src_usr;
13525
13526 END;
13527 $$ LANGUAGE plpgsql;
13528
13529 -- The "add" trigger functions should protect against existing NULLed values, just in case
13530 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13531 BEGIN
13532     IF NOT NEW.voided THEN
13533         UPDATE  money.materialized_billable_xact_summary
13534           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13535             last_billing_ts = NEW.billing_ts,
13536             last_billing_note = NEW.note,
13537             last_billing_type = NEW.billing_type,
13538             balance_owed = balance_owed + NEW.amount
13539           WHERE id = NEW.xact;
13540     END IF;
13541
13542     RETURN NEW;
13543 END;
13544 $$ LANGUAGE PLPGSQL;
13545
13546 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13547 BEGIN
13548     IF NOT NEW.voided THEN
13549         UPDATE  money.materialized_billable_xact_summary
13550           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13551             last_payment_ts = NEW.payment_ts,
13552             last_payment_note = NEW.note,
13553             last_payment_type = TG_ARGV[0],
13554             balance_owed = balance_owed - NEW.amount
13555           WHERE id = NEW.xact;
13556     END IF;
13557
13558     RETURN NEW;
13559 END;
13560 $$ LANGUAGE PLPGSQL;
13561
13562 -- Refresh the mat view with the corrected underlying view
13563 TRUNCATE money.materialized_billable_xact_summary;
13564 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13565
13566 -- Now redefine the view as a window onto the materialized view
13567 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13568     SELECT * FROM money.materialized_billable_xact_summary;
13569
13570 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13571     user_id    IN INTEGER,
13572     perm_code  IN TEXT
13573 )
13574 RETURNS SETOF INTEGER AS $$
13575 --
13576 -- Return a set of all the org units for which a given user has a given
13577 -- permission, granted directly (not through inheritance from a parent
13578 -- org unit).
13579 --
13580 -- The permissions apply to a minimum depth of the org unit hierarchy,
13581 -- for the org unit(s) to which the user is assigned.  (They also apply
13582 -- to the subordinates of those org units, but we don't report the
13583 -- subordinates here.)
13584 --
13585 -- For purposes of this function, the permission.usr_work_ou_map table
13586 -- defines which users belong to which org units.  I.e. we ignore the
13587 -- home_ou column of actor.usr.
13588 --
13589 -- The result set may contain duplicates, which should be eliminated
13590 -- by a DISTINCT clause.
13591 --
13592 DECLARE
13593     b_super       BOOLEAN;
13594     n_perm        INTEGER;
13595     n_min_depth   INTEGER;
13596     n_work_ou     INTEGER;
13597     n_curr_ou     INTEGER;
13598     n_depth       INTEGER;
13599     n_curr_depth  INTEGER;
13600 BEGIN
13601     --
13602     -- Check for superuser
13603     --
13604     SELECT INTO b_super
13605         super_user
13606     FROM
13607         actor.usr
13608     WHERE
13609         id = user_id;
13610     --
13611     IF NOT FOUND THEN
13612         return;             -- No user?  No permissions.
13613     ELSIF b_super THEN
13614         --
13615         -- Super user has all permissions everywhere
13616         --
13617         FOR n_work_ou IN
13618             SELECT
13619                 id
13620             FROM
13621                 actor.org_unit
13622             WHERE
13623                 parent_ou IS NULL
13624         LOOP
13625             RETURN NEXT n_work_ou;
13626         END LOOP;
13627         RETURN;
13628     END IF;
13629     --
13630     -- Translate the permission name
13631     -- to a numeric permission id
13632     --
13633     SELECT INTO n_perm
13634         id
13635     FROM
13636         permission.perm_list
13637     WHERE
13638         code = perm_code;
13639     --
13640     IF NOT FOUND THEN
13641         RETURN;               -- No such permission
13642     END IF;
13643     --
13644     -- Find the highest-level org unit (i.e. the minimum depth)
13645     -- to which the permission is applied for this user
13646     --
13647     -- This query is modified from the one in permission.usr_perms().
13648     --
13649     SELECT INTO n_min_depth
13650         min( depth )
13651     FROM    (
13652         SELECT depth
13653           FROM permission.usr_perm_map upm
13654          WHERE upm.usr = user_id
13655            AND (upm.perm = n_perm OR upm.perm = -1)
13656                     UNION
13657         SELECT  gpm.depth
13658           FROM  permission.grp_perm_map gpm
13659           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13660             AND gpm.grp IN (
13661                SELECT   (permission.grp_ancestors(
13662                     (SELECT profile FROM actor.usr WHERE id = user_id)
13663                 )).id
13664             )
13665                     UNION
13666         SELECT  p.depth
13667           FROM  permission.grp_perm_map p
13668           WHERE (p.perm = n_perm OR p.perm = -1)
13669             AND p.grp IN (
13670                 SELECT (permission.grp_ancestors(m.grp)).id
13671                 FROM   permission.usr_grp_map m
13672                 WHERE  m.usr = user_id
13673             )
13674     ) AS x;
13675     --
13676     IF NOT FOUND THEN
13677         RETURN;                -- No such permission for this user
13678     END IF;
13679     --
13680     -- Identify the org units to which the user is assigned.  Note that
13681     -- we pay no attention to the home_ou column in actor.usr.
13682     --
13683     FOR n_work_ou IN
13684         SELECT
13685             work_ou
13686         FROM
13687             permission.usr_work_ou_map
13688         WHERE
13689             usr = user_id
13690     LOOP            -- For each org unit to which the user is assigned
13691         --
13692         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13693         -- We take it on faith that this depth agrees with the actual hierarchy
13694         -- defined in actor.org_unit.
13695         --
13696         SELECT INTO n_depth
13697             type.depth
13698         FROM
13699             actor.org_unit_type type
13700                 INNER JOIN actor.org_unit ou
13701                     ON ( ou.ou_type = type.id )
13702         WHERE
13703             ou.id = n_work_ou;
13704         --
13705         IF NOT FOUND THEN
13706             CONTINUE;        -- Maybe raise exception?
13707         END IF;
13708         --
13709         -- Compare the depth of the work org unit to the
13710         -- minimum depth, and branch accordingly
13711         --
13712         IF n_depth = n_min_depth THEN
13713             --
13714             -- The org unit is at the right depth, so return it.
13715             --
13716             RETURN NEXT n_work_ou;
13717         ELSIF n_depth > n_min_depth THEN
13718             --
13719             -- Traverse the org unit tree toward the root,
13720             -- until you reach the minimum depth determined above
13721             --
13722             n_curr_depth := n_depth;
13723             n_curr_ou := n_work_ou;
13724             WHILE n_curr_depth > n_min_depth LOOP
13725                 SELECT INTO n_curr_ou
13726                     parent_ou
13727                 FROM
13728                     actor.org_unit
13729                 WHERE
13730                     id = n_curr_ou;
13731                 --
13732                 IF FOUND THEN
13733                     n_curr_depth := n_curr_depth - 1;
13734                 ELSE
13735                     --
13736                     -- This can happen only if the hierarchy defined in
13737                     -- actor.org_unit is corrupted, or out of sync with
13738                     -- the depths defined in actor.org_unit_type.
13739                     -- Maybe we should raise an exception here, instead
13740                     -- of silently ignoring the problem.
13741                     --
13742                     n_curr_ou = NULL;
13743                     EXIT;
13744                 END IF;
13745             END LOOP;
13746             --
13747             IF n_curr_ou IS NOT NULL THEN
13748                 RETURN NEXT n_curr_ou;
13749             END IF;
13750         ELSE
13751             --
13752             -- The permission applies only at a depth greater than the work org unit.
13753             -- Use connectby() to find all dependent org units at the specified depth.
13754             --
13755             FOR n_curr_ou IN
13756                 SELECT ou::INTEGER
13757                 FROM connectby(
13758                         'actor.org_unit',         -- table name
13759                         'id',                     -- key column
13760                         'parent_ou',              -- recursive foreign key
13761                         n_work_ou::TEXT,          -- id of starting point
13762                         (n_min_depth - n_depth)   -- max depth to search, relative
13763                     )                             --   to starting point
13764                     AS t(
13765                         ou text,            -- dependent org unit
13766                         parent_ou text,     -- (ignore)
13767                         level int           -- depth relative to starting point
13768                     )
13769                 WHERE
13770                     level = n_min_depth - n_depth
13771             LOOP
13772                 RETURN NEXT n_curr_ou;
13773             END LOOP;
13774         END IF;
13775         --
13776     END LOOP;
13777     --
13778     RETURN;
13779     --
13780 END;
13781 $$ LANGUAGE 'plpgsql';
13782
13783 ALTER TABLE acq.purchase_order
13784         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
13785                                             DEFERRABLE INITIALLY DEFERRED;
13786
13787 ALTER TABLE acq.acq_purchase_order_history
13788         ADD COLUMN cancel_reason INTEGER;
13789
13790 ALTER TABLE acq.purchase_order
13791         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
13792
13793 ALTER TABLE acq.acq_purchase_order_history
13794         ADD COLUMN prepayment_required BOOLEAN;
13795
13796 DROP VIEW IF EXISTS acq.purchase_order_lifecycle;
13797
13798 SELECT acq.create_acq_lifecycle( 'acq', 'purchase_order' );
13799
13800 ALTER TABLE acq.lineitem
13801         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
13802                                             DEFERRABLE INITIALLY DEFERRED;
13803
13804 ALTER TABLE acq.acq_lineitem_history
13805         ADD COLUMN cancel_reason INTEGER;
13806
13807 ALTER TABLE acq.lineitem
13808         ADD COLUMN estimated_unit_price NUMERIC;
13809
13810 ALTER TABLE acq.acq_lineitem_history
13811         ADD COLUMN estimated_unit_price NUMERIC;
13812
13813 ALTER TABLE acq.lineitem
13814         ADD COLUMN claim_policy INT
13815                 REFERENCES acq.claim_policy
13816                 DEFERRABLE INITIALLY DEFERRED;
13817
13818 ALTER TABLE acq.acq_lineitem_history
13819         ADD COLUMN claim_policy INT;
13820
13821 -- Rebuild the lifecycle view
13822
13823 DROP VIEW IF EXISTS acq.acq_lineitem_lifecycle;
13824
13825 SELECT acq.create_acq_lifecycle( 'acq', 'lineitem' );
13826
13827 ALTER TABLE acq.lineitem_detail
13828         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
13829                                             DEFERRABLE INITIALLY DEFERRED;
13830
13831 ALTER TABLE acq.lineitem_detail
13832         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
13833
13834 ALTER TABLE acq.lineitem_detail
13835         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13836                 ON DELETE CASCADE
13837                 DEFERRABLE INITIALLY DEFERRED;
13838
13839 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
13840
13841 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
13842         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
13843
13844 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
13845         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
13846
13847 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
13848
13849     use MARC::Record;
13850     use MARC::File::XML (BinaryEncoding => 'UTF-8');
13851     use strict;
13852
13853     my $target_xml = shift;
13854     my $source_xml = shift;
13855     my $field_spec = shift;
13856
13857     my $target_r = MARC::Record->new_from_xml( $target_xml );
13858     my $source_r = MARC::Record->new_from_xml( $source_xml );
13859
13860     return $target_xml unless ($target_r && $source_r);
13861
13862     my @field_list = split(',', $field_spec);
13863
13864     my %fields;
13865     for my $f (@field_list) {
13866         $f =~ s/^\s*//; $f =~ s/\s*$//;
13867         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
13868             my $field = $1;
13869             $field =~ s/\s+//;
13870             my $sf = $2;
13871             $sf =~ s/\s+//;
13872             my $match = $3;
13873             $match =~ s/^\s*//; $match =~ s/\s*$//;
13874             $fields{$field} = { sf => [ split('', $sf) ] };
13875             if ($match) {
13876                 my ($msf,$mre) = split('~', $match);
13877                 if (length($msf) > 0 and length($mre) > 0) {
13878                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
13879                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
13880                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
13881                 }
13882             }
13883         }
13884     }
13885
13886     for my $f ( keys %fields) {
13887         if ( @{$fields{$f}{sf}} ) {
13888             for my $from_field ($source_r->field( $f )) {
13889                 for my $to_field ($target_r->field( $f )) {
13890                     if (exists($fields{$f}{match})) {
13891                         next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
13892                     }
13893                     my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
13894                     $to_field->add_subfields( @new_sf );
13895                 }
13896             }
13897         } else {
13898             my @new_fields = map { $_->clone } $source_r->field( $f );
13899             $target_r->insert_fields_ordered( @new_fields );
13900         }
13901     }
13902
13903     $target_xml = $target_r->as_xml_record;
13904     $target_xml =~ s/^<\?.+?\?>$//mo;
13905     $target_xml =~ s/\n//sgo;
13906     $target_xml =~ s/>\s+</></sgo;
13907
13908     return $target_xml;
13909
13910 $_$ LANGUAGE PLPERLU;
13911
13912 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
13913
13914     use MARC::Record;
13915     use MARC::File::XML (BinaryEncoding => 'UTF-8');
13916     use strict;
13917
13918     my $xml = shift;
13919     my $r = MARC::Record->new_from_xml( $xml );
13920
13921     return $xml unless ($r);
13922
13923     my $field_spec = shift;
13924     my @field_list = split(',', $field_spec);
13925
13926     my %fields;
13927     for my $f (@field_list) {
13928         $f =~ s/^\s*//; $f =~ s/\s*$//;
13929         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
13930             my $field = $1;
13931             $field =~ s/\s+//;
13932             my $sf = $2;
13933             $sf =~ s/\s+//;
13934             my $match = $3;
13935             $match =~ s/^\s*//; $match =~ s/\s*$//;
13936             $fields{$field} = { sf => [ split('', $sf) ] };
13937             if ($match) {
13938                 my ($msf,$mre) = split('~', $match);
13939                 if (length($msf) > 0 and length($mre) > 0) {
13940                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
13941                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
13942                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
13943                 }
13944             }
13945         }
13946     }
13947
13948     for my $f ( keys %fields) {
13949         for my $to_field ($r->field( $f )) {
13950             if (exists($fields{$f}{match})) {
13951                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
13952             }
13953
13954             if ( @{$fields{$f}{sf}} ) {
13955                 $to_field->delete_subfield(code => $fields{$f}{sf});
13956             } else {
13957                 $r->delete_field( $to_field );
13958             }
13959         }
13960     }
13961
13962     $xml = $r->as_xml_record;
13963     $xml =~ s/^<\?.+?\?>$//mo;
13964     $xml =~ s/\n//sgo;
13965     $xml =~ s/>\s+</></sgo;
13966
13967     return $xml;
13968
13969 $_$ LANGUAGE PLPERLU;
13970
13971 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
13972     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
13973 $_$ LANGUAGE SQL;
13974
13975 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
13976     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
13977 $_$ LANGUAGE SQL;
13978
13979 CREATE VIEW action.unfulfilled_hold_max_loop AS
13980         SELECT  hold,
13981                 max(count) AS max
13982         FROM    action.unfulfilled_hold_loops
13983         GROUP BY 1;
13984
13985 ALTER TABLE acq.lineitem_attr
13986         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
13987
13988 ALTER TABLE acq.lineitem_attr
13989         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13990                 ON DELETE CASCADE
13991                 DEFERRABLE INITIALLY DEFERRED;
13992
13993 ALTER TABLE acq.po_note
13994         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13995
13996 CREATE TABLE vandelay.merge_profile (
13997     id              BIGSERIAL   PRIMARY KEY,
13998     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
13999     name            TEXT        NOT NULL,
14000     add_spec        TEXT,
14001     replace_spec    TEXT,
14002     strip_spec      TEXT,
14003     preserve_spec   TEXT,
14004     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14005     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))
14006 );
14007
14008 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14009 DECLARE
14010     attr        RECORD;
14011     attr_def    RECORD;
14012     eg_rec      RECORD;
14013     id_value    TEXT;
14014     exact_id    BIGINT;
14015 BEGIN
14016
14017     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14018
14019     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14020
14021     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14022         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14023
14024         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14025             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14026             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14027             IF exact_id IS NOT NULL THEN
14028                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14029             END IF;
14030         END IF;
14031     END IF;
14032
14033     IF exact_id IS NULL THEN
14034         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
14035
14036             -- All numbers? check for an id match
14037             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14038                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14039                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14040                 END LOOP;
14041             END IF;
14042
14043             -- Looks like an ISBN? check for an isbn match
14044             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14045                 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
14046                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14047                     IF FOUND THEN
14048                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14049                     END IF;
14050                 END LOOP;
14051
14052                 -- subcheck for isbn-as-tcn
14053                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14054                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14055                 END LOOP;
14056             END IF;
14057
14058             -- check for an OCLC tcn_value match
14059             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14060                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14061                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14062                 END LOOP;
14063             END IF;
14064
14065             -- check for a direct tcn_value match
14066             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14067                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14068             END LOOP;
14069
14070             -- check for a direct item barcode match
14071             FOR eg_rec IN
14072                     SELECT  DISTINCT b.*
14073                       FROM  biblio.record_entry b
14074                             JOIN asset.call_number cn ON (cn.record = b.id)
14075                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14076                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14077             LOOP
14078                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14079             END LOOP;
14080
14081         END LOOP;
14082     END IF;
14083
14084     RETURN NULL;
14085 END;
14086 $func$ LANGUAGE PLPGSQL;
14087
14088 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 $_$
14089     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14090 $_$ LANGUAGE SQL;
14091
14092 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14093 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14094 DECLARE
14095     output              vandelay.compile_profile%ROWTYPE;
14096     profile             vandelay.merge_profile%ROWTYPE;
14097     profile_tmpl        TEXT;
14098     profile_tmpl_owner  TEXT;
14099     add_rule            TEXT := '';
14100     strip_rule          TEXT := '';
14101     replace_rule        TEXT := '';
14102     preserve_rule       TEXT := '';
14103
14104 BEGIN
14105
14106     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14107     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14108
14109     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14110         SELECT  p.* INTO profile
14111           FROM  vandelay.merge_profile p
14112                 JOIN actor.org_unit u ON (u.id = p.owner)
14113           WHERE p.name = profile_tmpl
14114                 AND u.shortname = profile_tmpl_owner;
14115
14116         IF profile.id IS NOT NULL THEN
14117             add_rule := COALESCE(profile.add_spec,'');
14118             strip_rule := COALESCE(profile.strip_spec,'');
14119             replace_rule := COALESCE(profile.replace_spec,'');
14120             preserve_rule := COALESCE(profile.preserve_spec,'');
14121         END IF;
14122     END IF;
14123
14124     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),''),'');
14125     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),''),'');
14126     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),''),'');
14127     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),''),'');
14128
14129     output.add_rule := BTRIM(add_rule,',');
14130     output.replace_rule := BTRIM(replace_rule,',');
14131     output.strip_rule := BTRIM(strip_rule,',');
14132     output.preserve_rule := BTRIM(preserve_rule,',');
14133
14134     RETURN output;
14135 END;
14136 $_$ LANGUAGE PLPGSQL;
14137
14138 -- Template-based marc munging functions
14139 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14140 DECLARE
14141     merge_profile   vandelay.merge_profile%ROWTYPE;
14142     dyn_profile     vandelay.compile_profile%ROWTYPE;
14143     editor_string   TEXT;
14144     editor_id       INT;
14145     source_marc     TEXT;
14146     target_marc     TEXT;
14147     eg_marc         TEXT;
14148     replace_rule    TEXT;
14149     match_count     INT;
14150 BEGIN
14151
14152     SELECT  b.marc INTO eg_marc
14153       FROM  biblio.record_entry b
14154       WHERE b.id = eg_id
14155       LIMIT 1;
14156
14157     IF eg_marc IS NULL OR v_marc IS NULL THEN
14158         -- RAISE NOTICE 'no marc for template or bib record';
14159         RETURN FALSE;
14160     END IF;
14161
14162     dyn_profile := vandelay.compile_profile( v_marc );
14163
14164     IF merge_profile_id IS NOT NULL THEN
14165         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14166         IF FOUND THEN
14167             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14168             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14169             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14170             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14171         END IF;
14172     END IF;
14173
14174     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14175         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14176         RETURN FALSE;
14177     END IF;
14178
14179     IF dyn_profile.replace_rule <> '' THEN
14180         source_marc = v_marc;
14181         target_marc = eg_marc;
14182         replace_rule = dyn_profile.replace_rule;
14183     ELSE
14184         source_marc = eg_marc;
14185         target_marc = v_marc;
14186         replace_rule = dyn_profile.preserve_rule;
14187     END IF;
14188
14189     UPDATE  biblio.record_entry
14190       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14191       WHERE id = eg_id;
14192
14193     IF NOT FOUND THEN
14194         -- RAISE NOTICE 'update of biblio.record_entry failed';
14195         RETURN FALSE;
14196     END IF;
14197
14198     RETURN TRUE;
14199
14200 END;
14201 $$ LANGUAGE PLPGSQL;
14202
14203 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14204     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14205 $$ LANGUAGE SQL;
14206
14207 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14208 DECLARE
14209     merge_profile   vandelay.merge_profile%ROWTYPE;
14210     dyn_profile     vandelay.compile_profile%ROWTYPE;
14211     editor_string   TEXT;
14212     editor_id       INT;
14213     source_marc     TEXT;
14214     target_marc     TEXT;
14215     eg_marc         TEXT;
14216     v_marc          TEXT;
14217     replace_rule    TEXT;
14218     match_count     INT;
14219 BEGIN
14220
14221     SELECT  q.marc INTO v_marc
14222       FROM  vandelay.queued_record q
14223             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14224       LIMIT 1;
14225
14226     IF v_marc IS NULL THEN
14227         -- RAISE NOTICE 'no marc for vandelay or bib record';
14228         RETURN FALSE;
14229     END IF;
14230
14231     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14232         UPDATE  vandelay.queued_bib_record
14233           SET   imported_as = eg_id,
14234                 import_time = NOW()
14235           WHERE id = import_id;
14236
14237         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14238
14239         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14240             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14241
14242             IF editor_id IS NULL THEN
14243                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14244             END IF;
14245
14246             IF editor_id IS NOT NULL THEN
14247                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14248             END IF;
14249         END IF;
14250
14251         RETURN TRUE;
14252     END IF;
14253
14254     -- RAISE NOTICE 'update of biblio.record_entry failed';
14255
14256     RETURN FALSE;
14257
14258 END;
14259 $$ LANGUAGE PLPGSQL;
14260
14261 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14262 DECLARE
14263     eg_id           BIGINT;
14264     match_count     INT;
14265     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14266 BEGIN
14267
14268     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14269
14270     IF FOUND THEN
14271         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14272         RETURN FALSE;
14273     END IF;
14274
14275     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14276
14277     IF match_count <> 1 THEN
14278         -- RAISE NOTICE 'not an exact match';
14279         RETURN FALSE;
14280     END IF;
14281
14282     SELECT  d.* INTO match_attr
14283       FROM  vandelay.bib_attr_definition d
14284             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14285             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14286       WHERE m.queued_record = import_id;
14287
14288     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14289         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14290         RETURN FALSE;
14291     END IF;
14292
14293     SELECT  m.eg_record INTO eg_id
14294       FROM  vandelay.bib_match m
14295       WHERE m.queued_record = import_id
14296       LIMIT 1;
14297
14298     IF eg_id IS NULL THEN
14299         RETURN FALSE;
14300     END IF;
14301
14302     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14303 END;
14304 $$ LANGUAGE PLPGSQL;
14305
14306 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14307 DECLARE
14308     queued_record   vandelay.queued_bib_record%ROWTYPE;
14309 BEGIN
14310
14311     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14312
14313         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14314             RETURN NEXT queued_record.id;
14315         END IF;
14316
14317     END LOOP;
14318
14319     RETURN;
14320
14321 END;
14322 $$ LANGUAGE PLPGSQL;
14323
14324 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14325     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14326 $$ LANGUAGE SQL;
14327
14328 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14329 DECLARE
14330     merge_profile   vandelay.merge_profile%ROWTYPE;
14331     dyn_profile     vandelay.compile_profile%ROWTYPE;
14332     source_marc     TEXT;
14333     target_marc     TEXT;
14334     eg_marc         TEXT;
14335     v_marc          TEXT;
14336     replace_rule    TEXT;
14337     match_count     INT;
14338 BEGIN
14339
14340     SELECT  b.marc INTO eg_marc
14341       FROM  authority.record_entry b
14342             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14343       LIMIT 1;
14344
14345     SELECT  q.marc INTO v_marc
14346       FROM  vandelay.queued_record q
14347             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14348       LIMIT 1;
14349
14350     IF eg_marc IS NULL OR v_marc IS NULL THEN
14351         -- RAISE NOTICE 'no marc for vandelay or authority record';
14352         RETURN FALSE;
14353     END IF;
14354
14355     dyn_profile := vandelay.compile_profile( v_marc );
14356
14357     IF merge_profile_id IS NOT NULL THEN
14358         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14359         IF FOUND THEN
14360             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14361             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14362             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14363             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14364         END IF;
14365     END IF;
14366
14367     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14368         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14369         RETURN FALSE;
14370     END IF;
14371
14372     IF dyn_profile.replace_rule <> '' THEN
14373         source_marc = v_marc;
14374         target_marc = eg_marc;
14375         replace_rule = dyn_profile.replace_rule;
14376     ELSE
14377         source_marc = eg_marc;
14378         target_marc = v_marc;
14379         replace_rule = dyn_profile.preserve_rule;
14380     END IF;
14381
14382     UPDATE  authority.record_entry
14383       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14384       WHERE id = eg_id;
14385
14386     IF FOUND THEN
14387         UPDATE  vandelay.queued_authority_record
14388           SET   imported_as = eg_id,
14389                 import_time = NOW()
14390           WHERE id = import_id;
14391         RETURN TRUE;
14392     END IF;
14393
14394     -- RAISE NOTICE 'update of authority.record_entry failed';
14395
14396     RETURN FALSE;
14397
14398 END;
14399 $$ LANGUAGE PLPGSQL;
14400
14401 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14402 DECLARE
14403     eg_id           BIGINT;
14404     match_count     INT;
14405 BEGIN
14406     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14407
14408     IF match_count <> 1 THEN
14409         -- RAISE NOTICE 'not an exact match';
14410         RETURN FALSE;
14411     END IF;
14412
14413     SELECT  m.eg_record INTO eg_id
14414       FROM  vandelay.authority_match m
14415       WHERE m.queued_record = import_id
14416       LIMIT 1;
14417
14418     IF eg_id IS NULL THEN
14419         RETURN FALSE;
14420     END IF;
14421
14422     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14423 END;
14424 $$ LANGUAGE PLPGSQL;
14425
14426 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14427 DECLARE
14428     queued_record   vandelay.queued_authority_record%ROWTYPE;
14429 BEGIN
14430
14431     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14432
14433         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14434             RETURN NEXT queued_record.id;
14435         END IF;
14436
14437     END LOOP;
14438
14439     RETURN;
14440
14441 END;
14442 $$ LANGUAGE PLPGSQL;
14443
14444 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14445     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14446 $$ LANGUAGE SQL;
14447
14448 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14449 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14450 DECLARE
14451     eg_tcn          TEXT;
14452     eg_tcn_source   TEXT;
14453     output          vandelay.tcn_data%ROWTYPE;
14454 BEGIN
14455
14456     -- 001/003
14457     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14458     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14459
14460         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14461         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14462             eg_tcn_source := 'System Local';
14463         END IF;
14464
14465         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14466
14467         IF NOT FOUND THEN
14468             output.used := FALSE;
14469         ELSE
14470             output.used := TRUE;
14471         END IF;
14472
14473         output.tcn := eg_tcn;
14474         output.tcn_source := eg_tcn_source;
14475         RETURN NEXT output;
14476
14477     END IF;
14478
14479     -- 901 ab
14480     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14481     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14482
14483         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14484         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14485             eg_tcn_source := 'System Local';
14486         END IF;
14487
14488         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14489
14490         IF NOT FOUND THEN
14491             output.used := FALSE;
14492         ELSE
14493             output.used := TRUE;
14494         END IF;
14495
14496         output.tcn := eg_tcn;
14497         output.tcn_source := eg_tcn_source;
14498         RETURN NEXT output;
14499
14500     END IF;
14501
14502     -- 039 ab
14503     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14504     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14505
14506         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14507         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14508             eg_tcn_source := 'System Local';
14509         END IF;
14510
14511         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14512
14513         IF NOT FOUND THEN
14514             output.used := FALSE;
14515         ELSE
14516             output.used := TRUE;
14517         END IF;
14518
14519         output.tcn := eg_tcn;
14520         output.tcn_source := eg_tcn_source;
14521         RETURN NEXT output;
14522
14523     END IF;
14524
14525     -- 020 a
14526     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14527     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14528
14529         eg_tcn_source := 'ISBN';
14530
14531         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14532
14533         IF NOT FOUND THEN
14534             output.used := FALSE;
14535         ELSE
14536             output.used := TRUE;
14537         END IF;
14538
14539         output.tcn := eg_tcn;
14540         output.tcn_source := eg_tcn_source;
14541         RETURN NEXT output;
14542
14543     END IF;
14544
14545     -- 022 a
14546     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14547     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14548
14549         eg_tcn_source := 'ISSN';
14550
14551         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14552
14553         IF NOT FOUND THEN
14554             output.used := FALSE;
14555         ELSE
14556             output.used := TRUE;
14557         END IF;
14558
14559         output.tcn := eg_tcn;
14560         output.tcn_source := eg_tcn_source;
14561         RETURN NEXT output;
14562
14563     END IF;
14564
14565     -- 010 a
14566     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14567     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14568
14569         eg_tcn_source := 'LCCN';
14570
14571         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14572
14573         IF NOT FOUND THEN
14574             output.used := FALSE;
14575         ELSE
14576             output.used := TRUE;
14577         END IF;
14578
14579         output.tcn := eg_tcn;
14580         output.tcn_source := eg_tcn_source;
14581         RETURN NEXT output;
14582
14583     END IF;
14584
14585     -- 035 a
14586     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
14587     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14588
14589         eg_tcn_source := 'System Legacy';
14590
14591         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14592
14593         IF NOT FOUND THEN
14594             output.used := FALSE;
14595         ELSE
14596             output.used := TRUE;
14597         END IF;
14598
14599         output.tcn := eg_tcn;
14600         output.tcn_source := eg_tcn_source;
14601         RETURN NEXT output;
14602
14603     END IF;
14604
14605     RETURN;
14606 END;
14607 $_$ LANGUAGE PLPGSQL;
14608
14609 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14610
14611 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);
14612
14613 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
14614
14615 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14616 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14617 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14618 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14619 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14620
14621 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14622 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14623 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14624 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14625
14626 ALTER TABLE metabib.series_field_entry
14627         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14628                 REFERENCES biblio.record_entry (id)
14629                 ON DELETE CASCADE
14630                 DEFERRABLE INITIALLY DEFERRED;
14631
14632 ALTER TABLE metabib.series_field_entry
14633         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14634                 REFERENCES config.metabib_field (id)
14635                 ON DELETE CASCADE
14636                 DEFERRABLE INITIALLY DEFERRED;
14637
14638 CREATE TABLE acq.claim_policy_action (
14639         id              SERIAL       PRIMARY KEY,
14640         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14641                                  ON DELETE CASCADE
14642                                      DEFERRABLE INITIALLY DEFERRED,
14643         action_interval INTERVAL     NOT NULL,
14644         action          INT          NOT NULL REFERENCES acq.claim_event_type
14645                                      DEFERRABLE INITIALLY DEFERRED,
14646         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14647 );
14648
14649 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14650 DECLARE
14651     value       TEXT;
14652     atype       TEXT;
14653     prov        INT;
14654     pos         INT;
14655     adef        RECORD;
14656     xpath_string    TEXT;
14657 BEGIN
14658     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14659  
14660         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14661  
14662         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14663             IF (atype = 'lineitem_provider_attr_definition') THEN
14664                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14665                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14666             END IF;
14667  
14668             IF (atype = 'lineitem_provider_attr_definition') THEN
14669                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14670             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14671                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14672             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14673                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14674             END IF;
14675  
14676             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14677  
14678             pos := 1;
14679  
14680             LOOP
14681                 SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14682  
14683                 IF (value IS NOT NULL AND value <> '') THEN
14684                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14685                         VALUES (NEW.id, adef.id, atype, adef.code, value);
14686                 ELSE
14687                     EXIT;
14688                 END IF;
14689  
14690                 pos := pos + 1;
14691             END LOOP;
14692  
14693         END IF;
14694  
14695     END LOOP;
14696  
14697     RETURN NULL;
14698 END;
14699 $function$ LANGUAGE PLPGSQL;
14700
14701 UPDATE config.metabib_field SET label = name;
14702 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14703
14704 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
14705          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
14706
14707 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
14708
14709 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
14710
14711 CREATE TABLE config.metabib_search_alias (
14712     alias       TEXT    PRIMARY KEY,
14713     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
14714     field       INT     REFERENCES config.metabib_field (id)
14715 );
14716
14717 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
14718 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
14719 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
14720 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
14721 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
14722 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
14723 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
14724 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
14725
14726 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
14727 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
14728 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
14729 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
14730 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
14731 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
14732 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
14733 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
14734 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
14735 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
14736 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
14737 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
14738 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
14739
14740 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
14741 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
14742 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
14743 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
14744 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
14745 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
14746 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
14747 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
14748
14749 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
14750 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
14751 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
14752 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
14753 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
14754 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
14755
14756 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
14757 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
14758 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
14759
14760 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
14761 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;
14762 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;
14763 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;
14764 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;
14765
14766 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
14767 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
14768 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
14769 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
14770
14771 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
14772 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
14773 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
14774 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
14775 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
14776 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
14777 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
14778 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
14779 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
14780 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
14781 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
14782 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
14783 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
14784 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
14785
14786 CREATE TABLE asset.opac_visible_copies (
14787   id        BIGINT primary key, -- copy id
14788   record    BIGINT,
14789   circ_lib  INTEGER
14790 );
14791 COMMENT ON TABLE asset.opac_visible_copies IS $$
14792 Materialized view of copies that are visible in the OPAC, used by
14793 search.query_parser_fts() to speed up OPAC visibility checks on large
14794 databases.  Contents are maintained by a set of triggers.
14795 $$;
14796 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
14797
14798 CREATE OR REPLACE FUNCTION search.query_parser_fts (
14799
14800     param_search_ou INT,
14801     param_depth     INT,
14802     param_query     TEXT,
14803     param_statuses  INT[],
14804     param_locations INT[],
14805     param_offset    INT,
14806     param_check     INT,
14807     param_limit     INT,
14808     metarecord      BOOL,
14809     staff           BOOL
14810  
14811 ) RETURNS SETOF search.search_result AS $func$
14812 DECLARE
14813
14814     current_res         search.search_result%ROWTYPE;
14815     search_org_list     INT[];
14816
14817     check_limit         INT;
14818     core_limit          INT;
14819     core_offset         INT;
14820     tmp_int             INT;
14821
14822     core_result         RECORD;
14823     core_cursor         REFCURSOR;
14824     core_rel_query      TEXT;
14825
14826     total_count         INT := 0;
14827     check_count         INT := 0;
14828     deleted_count       INT := 0;
14829     visible_count       INT := 0;
14830     excluded_count      INT := 0;
14831
14832 BEGIN
14833
14834     check_limit := COALESCE( param_check, 1000 );
14835     core_limit  := COALESCE( param_limit, 25000 );
14836     core_offset := COALESCE( param_offset, 0 );
14837
14838     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
14839
14840     IF param_search_ou > 0 THEN
14841         IF param_depth IS NOT NULL THEN
14842             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
14843         ELSE
14844             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
14845         END IF;
14846     ELSIF param_search_ou < 0 THEN
14847         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
14848     ELSIF param_search_ou = 0 THEN
14849         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
14850     END IF;
14851
14852     OPEN core_cursor FOR EXECUTE param_query;
14853
14854     LOOP
14855
14856         FETCH core_cursor INTO core_result;
14857         EXIT WHEN NOT FOUND;
14858         EXIT WHEN total_count >= core_limit;
14859
14860         total_count := total_count + 1;
14861
14862         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
14863
14864         check_count := check_count + 1;
14865
14866         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
14867         IF NOT FOUND THEN
14868             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
14869             deleted_count := deleted_count + 1;
14870             CONTINUE;
14871         END IF;
14872
14873         PERFORM 1
14874           FROM  biblio.record_entry b
14875                 JOIN config.bib_source s ON (b.source = s.id)
14876           WHERE s.transcendant
14877                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
14878
14879         IF FOUND THEN
14880             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
14881             visible_count := visible_count + 1;
14882
14883             current_res.id = core_result.id;
14884             current_res.rel = core_result.rel;
14885
14886             tmp_int := 1;
14887             IF metarecord THEN
14888                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
14889             END IF;
14890
14891             IF tmp_int = 1 THEN
14892                 current_res.record = core_result.records[1];
14893             ELSE
14894                 current_res.record = NULL;
14895             END IF;
14896
14897             RETURN NEXT current_res;
14898
14899             CONTINUE;
14900         END IF;
14901
14902         PERFORM 1
14903           FROM  asset.call_number cn
14904                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
14905                 JOIN asset.uri uri ON (map.uri = uri.id)
14906           WHERE NOT cn.deleted
14907                 AND cn.label = '##URI##'
14908                 AND uri.active
14909                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
14910                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
14911                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
14912           LIMIT 1;
14913
14914         IF FOUND THEN
14915             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
14916             visible_count := visible_count + 1;
14917
14918             current_res.id = core_result.id;
14919             current_res.rel = core_result.rel;
14920
14921             tmp_int := 1;
14922             IF metarecord THEN
14923                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
14924             END IF;
14925
14926             IF tmp_int = 1 THEN
14927                 current_res.record = core_result.records[1];
14928             ELSE
14929                 current_res.record = NULL;
14930             END IF;
14931
14932             RETURN NEXT current_res;
14933
14934             CONTINUE;
14935         END IF;
14936
14937         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
14938
14939             PERFORM 1
14940               FROM  asset.call_number cn
14941                     JOIN asset.copy cp ON (cp.call_number = cn.id)
14942               WHERE NOT cn.deleted
14943                     AND NOT cp.deleted
14944                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
14945                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
14946                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
14947               LIMIT 1;
14948
14949             IF NOT FOUND THEN
14950                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
14951                 excluded_count := excluded_count + 1;
14952                 CONTINUE;
14953             END IF;
14954
14955         END IF;
14956
14957         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
14958
14959             PERFORM 1
14960               FROM  asset.call_number cn
14961                     JOIN asset.copy cp ON (cp.call_number = cn.id)
14962               WHERE NOT cn.deleted
14963                     AND NOT cp.deleted
14964                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
14965                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
14966                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
14967               LIMIT 1;
14968
14969             IF NOT FOUND THEN
14970                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
14971                 excluded_count := excluded_count + 1;
14972                 CONTINUE;
14973             END IF;
14974
14975         END IF;
14976
14977         IF staff IS NULL OR NOT staff THEN
14978
14979             PERFORM 1
14980               FROM  asset.opac_visible_copies
14981               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
14982                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
14983               LIMIT 1;
14984
14985             IF NOT FOUND THEN
14986                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
14987                 excluded_count := excluded_count + 1;
14988                 CONTINUE;
14989             END IF;
14990
14991         ELSE
14992
14993             PERFORM 1
14994               FROM  asset.call_number cn
14995                     JOIN asset.copy cp ON (cp.call_number = cn.id)
14996                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
14997               WHERE NOT cn.deleted
14998                     AND NOT cp.deleted
14999                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15000                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15001               LIMIT 1;
15002
15003             IF NOT FOUND THEN
15004
15005                 PERFORM 1
15006                   FROM  asset.call_number cn
15007                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15008                   LIMIT 1;
15009
15010                 IF FOUND THEN
15011                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15012                     excluded_count := excluded_count + 1;
15013                     CONTINUE;
15014                 END IF;
15015
15016             END IF;
15017
15018         END IF;
15019
15020         visible_count := visible_count + 1;
15021
15022         current_res.id = core_result.id;
15023         current_res.rel = core_result.rel;
15024
15025         tmp_int := 1;
15026         IF metarecord THEN
15027             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15028         END IF;
15029
15030         IF tmp_int = 1 THEN
15031             current_res.record = core_result.records[1];
15032         ELSE
15033             current_res.record = NULL;
15034         END IF;
15035
15036         RETURN NEXT current_res;
15037
15038         IF visible_count % 1000 = 0 THEN
15039             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15040         END IF;
15041
15042     END LOOP;
15043
15044     current_res.id = NULL;
15045     current_res.rel = NULL;
15046     current_res.record = NULL;
15047     current_res.total = total_count;
15048     current_res.checked = check_count;
15049     current_res.deleted = deleted_count;
15050     current_res.visible = visible_count;
15051     current_res.excluded = excluded_count;
15052
15053     CLOSE core_cursor;
15054
15055     RETURN NEXT current_res;
15056
15057 END;
15058 $func$ LANGUAGE PLPGSQL;
15059
15060 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15061 ALTER TABLE biblio.record_entry
15062          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15063          REFERENCES actor.org_unit (id)
15064          DEFERRABLE INITIALLY DEFERRED;
15065
15066 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15067
15068 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15069 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15070
15071 DROP VIEW auditor.biblio_record_entry_lifecycle;
15072
15073 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15074
15075 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15076         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15077 $$ LANGUAGE SQL STRICT IMMUTABLE;
15078
15079 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15080     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15081 $$ LANGUAGE SQL STRICT IMMUTABLE;
15082
15083 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15084     return lc(shift);
15085 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15086
15087 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15088     return uc(shift);
15089 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15090
15091 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15092     use Unicode::Normalize;
15093
15094     my $x = NFD(shift);
15095     $x =~ s/\pM+//go;
15096     return $x;
15097
15098 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15099
15100 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15101     use Unicode::Normalize;
15102
15103     my $x = NFC(shift);
15104     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15105     return $x;
15106
15107 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15108
15109 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15110 DECLARE
15111     setting RECORD;
15112     cur_org INT;
15113 BEGIN
15114     cur_org := org_id;
15115     LOOP
15116         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15117         IF FOUND THEN
15118             RETURN NEXT setting;
15119         END IF;
15120         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15121         EXIT WHEN cur_org IS NULL;
15122     END LOOP;
15123     RETURN;
15124 END;
15125 $$ LANGUAGE plpgsql STABLE;
15126
15127 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15128 DECLARE
15129     counter INT;
15130     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15131 BEGIN
15132
15133     SELECT  COUNT(*) INTO counter
15134       FROM  oils_xpath_table(
15135                 'id',
15136                 'marc',
15137                 'acq.lineitem',
15138                 '//*[@tag="' || tag || '"]',
15139                 'id=' || lineitem
15140             ) as t(i int,c text);
15141
15142     FOR i IN 1 .. counter LOOP
15143         FOR lida IN
15144             SELECT  *
15145               FROM  (   SELECT  id,i,t,v
15146                           FROM  oils_xpath_table(
15147                                     'id',
15148                                     'marc',
15149                                     'acq.lineitem',
15150                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15151                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15152                                     'id=' || lineitem
15153                                 ) as t(id int,t text,v text)
15154                     )x
15155         LOOP
15156             RETURN NEXT lida;
15157         END LOOP;
15158     END LOOP;
15159
15160     RETURN;
15161 END;
15162 $$ LANGUAGE PLPGSQL;
15163
15164 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15165 DECLARE
15166     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15167     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15168     result      config.i18n_core%ROWTYPE;
15169     fallback    TEXT;
15170     keyfield    TEXT := keyclass || '.' || keycol;
15171 BEGIN
15172
15173     -- Try the full locale
15174     SELECT  * INTO result
15175       FROM  config.i18n_core
15176       WHERE fq_field = keyfield
15177             AND identity_value = keyvalue
15178             AND translation = locale;
15179
15180     -- Try just the language
15181     IF NOT FOUND THEN
15182         SELECT  * INTO result
15183           FROM  config.i18n_core
15184           WHERE fq_field = keyfield
15185                 AND identity_value = keyvalue
15186                 AND translation = language;
15187     END IF;
15188
15189     -- Fall back to the string we passed in in the first place
15190     IF NOT FOUND THEN
15191     EXECUTE
15192             'SELECT ' ||
15193                 keycol ||
15194             ' FROM ' || keytable ||
15195             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15196                 INTO fallback;
15197         RETURN fallback;
15198     END IF;
15199
15200     RETURN result.string;
15201 END;
15202 $func$ LANGUAGE PLPGSQL STABLE;
15203
15204 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15205
15206 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15207
15208 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15209
15210 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15211     3, 1, 'delivered_but_lost',
15212     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15213
15214 CREATE TABLE config.global_flag (
15215     label   TEXT    NOT NULL
15216 ) INHERITS (config.internal_flag);
15217 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15218
15219 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
15220     VALUES (
15221         'cat.bib.use_id_for_tcn',
15222         oils_i18n_gettext(
15223             'cat.bib.use_id_for_tcn',
15224             'Cat: Use Internal ID for TCN Value',
15225             'cgf', 
15226             'label'
15227         )
15228     );
15229
15230 -- resolves performance issue noted by EG Indiana
15231
15232 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15233
15234 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15235
15236 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15237     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15238 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15239     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15240 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15241     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15242 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15243     (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 );
15244 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15245     (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 );
15246 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15247     (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 );
15248 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15249     (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 );
15250 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15251     (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 );
15252 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15253     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15254
15255 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15256  
15257
15258 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15259
15260 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15261 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15262 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15263 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15264 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15265 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15266
15267 CREATE TABLE metabib.identifier_field_entry (
15268         id              BIGSERIAL       PRIMARY KEY,
15269         source          BIGINT          NOT NULL,
15270         field           INT             NOT NULL,
15271         value           TEXT            NOT NULL,
15272         index_vector    tsvector        NOT NULL
15273 );
15274 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15275         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15276         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15277
15278 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15279 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15280     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15281 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15282
15283 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15284     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15285 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15286     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15287
15288 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15289     use Business::ISBN;
15290     use strict;
15291     use warnings;
15292
15293     # For each ISBN found in a single string containing a set of ISBNs:
15294     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15295     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15296
15297     my $input = shift;
15298     my $output = '';
15299
15300     foreach my $word (split(/\s/, $input)) {
15301         my $isbn = Business::ISBN->new($word);
15302
15303         # First check the checksum; if it is not valid, fix it and add the original
15304         # bad-checksum ISBN to the output
15305         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15306             $output .= $isbn->isbn() . " ";
15307             $isbn->fix_checksum();
15308         }
15309
15310         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15311         # and add the normalized original ISBN to the output
15312         if ($isbn && $isbn->is_valid()) {
15313             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15314             $output .= $isbn->isbn . " ";
15315
15316             # If we successfully converted the ISBN to its counterpart, add the
15317             # converted ISBN to the output as well
15318             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15319         }
15320     }
15321     return $output if $output;
15322
15323     # If there were no valid ISBNs, just return the raw input
15324     return $input;
15325 $func$ LANGUAGE PLPERLU;
15326
15327 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15328 /*
15329  * Copyright (C) 2010 Merrimack Valley Library Consortium
15330  * Jason Stephenson <jstephenson@mvlc.org>
15331  * Copyright (C) 2010 Laurentian University
15332  * Dan Scott <dscott@laurentian.ca>
15333  *
15334  * The translate_isbn1013 function takes an input ISBN and returns the
15335  * following in a single space-delimited string if the input ISBN is valid:
15336  *   - The normalized input ISBN (hyphens stripped)
15337  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15338  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15339  */
15340 $$;
15341
15342 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15343 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15344 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15345 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15346 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15347 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15348
15349 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15350         'ISBN 10/13 conversion',
15351         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15352         'translate_isbn1013',
15353         0
15354 );
15355
15356 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15357         'Replace',
15358         'Replace all occurences of first parameter in the string with the second parameter.',
15359         'replace',
15360         2
15361 );
15362
15363 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15364     SELECT  m.id, i.id, 1
15365       FROM  config.metabib_field m,
15366             config.index_normalizer i
15367       WHERE i.func IN ('first_word')
15368             AND m.id IN (18);
15369
15370 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15371     SELECT  m.id, i.id, 2
15372       FROM  config.metabib_field m,
15373             config.index_normalizer i
15374       WHERE i.func IN ('translate_isbn1013')
15375             AND m.id IN (18);
15376
15377 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15378     SELECT  m.id, i.id, $$['-','']$$
15379       FROM  config.metabib_field m,
15380             config.index_normalizer i
15381       WHERE i.func IN ('replace')
15382             AND m.id IN (19);
15383
15384 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15385     SELECT  m.id, i.id, $$[' ','']$$
15386       FROM  config.metabib_field m,
15387             config.index_normalizer i
15388       WHERE i.func IN ('replace')
15389             AND m.id IN (19);
15390
15391 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15392
15393 UPDATE  config.metabib_field_index_norm_map
15394   SET   params = REPLACE(params,E'\'','"')
15395   WHERE params IS NOT NULL AND params <> '';
15396
15397 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15398
15399 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15400
15401 ALTER TABLE config.circ_modifier
15402         ADD COLUMN avg_wait_time INTERVAL;
15403
15404 --CREATE TABLE actor.usr_password_reset (
15405 --  id SERIAL PRIMARY KEY,
15406 --  uuid TEXT NOT NULL, 
15407 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15408 --  request_time TIMESTAMP NOT NULL DEFAULT NOW(), 
15409 --  has_been_reset BOOL NOT NULL DEFAULT false
15410 --);
15411 --COMMENT ON TABLE actor.usr_password_reset IS $$
15412 --/*
15413 -- * Copyright (C) 2010 Laurentian University
15414 -- * Dan Scott <dscott@laurentian.ca>
15415 -- *
15416 -- * Self-serve password reset requests
15417 -- *
15418 -- * ****
15419 -- *
15420 -- * This program is free software; you can redistribute it and/or
15421 -- * modify it under the terms of the GNU General Public License
15422 -- * as published by the Free Software Foundation; either version 2
15423 -- * of the License, or (at your option) any later version.
15424 -- *
15425 -- * This program is distributed in the hope that it will be useful,
15426 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15427 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15428 -- * GNU General Public License for more details.
15429 -- */
15430 --$$;
15431 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15432 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15433 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15434 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15435
15436 -- Use the identifier search class tsconfig
15437 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15438 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15439     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15440     FOR EACH ROW
15441     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15442
15443 INSERT INTO config.global_flag (name,label,enabled)
15444     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15445 INSERT INTO config.global_flag (name,label,enabled)
15446     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15447
15448 -- turn a JSON scalar into an SQL TEXT value
15449 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15450     use JSON::XS;                    
15451     my $json = shift();
15452     my $txt;
15453     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15454     return undef if ($@);
15455     return $txt
15456 $f$ LANGUAGE PLPERLU;
15457
15458 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15459 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15460 DECLARE
15461     c               action.circulation%ROWTYPE;
15462     view_age        INTERVAL;
15463     usr_view_age    actor.usr_setting%ROWTYPE;
15464     usr_view_start  actor.usr_setting%ROWTYPE;
15465 BEGIN
15466     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15467     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15468
15469     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15470         -- User opted in and supplied a retention age
15471         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15472             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15473         ELSE
15474             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15475         END IF;
15476     ELSIF usr_view_start.value IS NOT NULL THEN
15477         -- User opted in
15478         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15479     ELSE
15480         -- User did not opt in
15481         RETURN;
15482     END IF;
15483
15484     FOR c IN
15485         SELECT  *
15486           FROM  action.circulation
15487           WHERE usr = usr_id
15488                 AND parent_circ IS NULL
15489                 AND xact_start > NOW() - view_age
15490           ORDER BY xact_start
15491     LOOP
15492         RETURN NEXT c;
15493     END LOOP;
15494
15495     RETURN;
15496 END;
15497 $func$ LANGUAGE PLPGSQL;
15498
15499 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15500 DECLARE
15501     usr_keep_age    actor.usr_setting%ROWTYPE;
15502     usr_keep_start  actor.usr_setting%ROWTYPE;
15503     org_keep_age    INTERVAL;
15504     org_keep_count  INT;
15505
15506     keep_age        INTERVAL;
15507
15508     target_acp      RECORD;
15509     circ_chain_head action.circulation%ROWTYPE;
15510     circ_chain_tail action.circulation%ROWTYPE;
15511
15512     purge_position  INT;
15513     count_purged    INT;
15514 BEGIN
15515
15516     count_purged := 0;
15517
15518     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15519
15520     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15521     IF org_keep_count IS NULL THEN
15522         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15523     END IF;
15524
15525     -- First, find copies with more than keep_count non-renewal circs
15526     FOR target_acp IN
15527         SELECT  target_copy,
15528                 COUNT(*) AS total_real_circs
15529           FROM  action.circulation
15530           WHERE parent_circ IS NULL
15531                 AND xact_finish IS NOT NULL
15532           GROUP BY target_copy
15533           HAVING COUNT(*) > org_keep_count
15534     LOOP
15535         purge_position := 0;
15536         -- And, for those, select circs that are finished and older than keep_age
15537         FOR circ_chain_head IN
15538             SELECT  *
15539               FROM  action.circulation
15540               WHERE target_copy = target_acp.target_copy
15541                     AND parent_circ IS NULL
15542               ORDER BY xact_start
15543         LOOP
15544
15545             -- Stop once we've purged enough circs to hit org_keep_count
15546             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15547
15548             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15549             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15550
15551             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15552             usr_keep_age.value := NULL;
15553             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15554
15555             usr_keep_start.value := NULL;
15556             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15557
15558             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15559                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15560                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15561                 ELSE
15562                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15563                 END IF;
15564             ELSIF usr_keep_start.value IS NOT NULL THEN
15565                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15566             ELSE
15567                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15568             END IF;
15569
15570             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15571
15572             -- We've passed the purging tests, purge the circ chain starting at the end
15573             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15574             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15575                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15576                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15577             END LOOP;
15578
15579             count_purged := count_purged + 1;
15580             purge_position := purge_position + 1;
15581
15582         END LOOP;
15583     END LOOP;
15584 END;
15585 $func$ LANGUAGE PLPGSQL;
15586
15587 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15588 DECLARE
15589     h               action.hold_request%ROWTYPE;
15590     view_age        INTERVAL;
15591     view_count      INT;
15592     usr_view_count  actor.usr_setting%ROWTYPE;
15593     usr_view_age    actor.usr_setting%ROWTYPE;
15594     usr_view_start  actor.usr_setting%ROWTYPE;
15595 BEGIN
15596     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15597     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15598     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15599
15600     FOR h IN
15601         SELECT  *
15602           FROM  action.hold_request
15603           WHERE usr = usr_id
15604                 AND fulfillment_time IS NULL
15605                 AND cancel_time IS NULL
15606           ORDER BY request_time DESC
15607     LOOP
15608         RETURN NEXT h;
15609     END LOOP;
15610
15611     IF usr_view_start.value IS NULL THEN
15612         RETURN;
15613     END IF;
15614
15615     IF usr_view_age.value IS NOT NULL THEN
15616         -- User opted in and supplied a retention age
15617         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15618             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15619         ELSE
15620             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15621         END IF;
15622     ELSE
15623         -- User opted in
15624         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15625     END IF;
15626
15627     IF usr_view_count.value IS NOT NULL THEN
15628         view_count := oils_json_to_text(usr_view_count.value)::INT;
15629     ELSE
15630         view_count := 1000;
15631     END IF;
15632
15633     -- show some fulfilled/canceled holds
15634     FOR h IN
15635         SELECT  *
15636           FROM  action.hold_request
15637           WHERE usr = usr_id
15638                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
15639                 AND request_time > NOW() - view_age
15640           ORDER BY request_time DESC
15641           LIMIT view_count
15642     LOOP
15643         RETURN NEXT h;
15644     END LOOP;
15645
15646     RETURN;
15647 END;
15648 $func$ LANGUAGE PLPGSQL;
15649
15650 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
15651
15652 DROP TABLE IF EXISTS serial.index_summary CASCADE;
15653
15654 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
15655
15656 DROP TABLE IF EXISTS serial.issuance CASCADE;
15657
15658 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
15659
15660 DROP TABLE IF EXISTS serial.subscription CASCADE;
15661
15662 CREATE TABLE asset.copy_template (
15663         id             SERIAL   PRIMARY KEY,
15664         owning_lib     INT      NOT NULL
15665                                 REFERENCES actor.org_unit (id)
15666                                 DEFERRABLE INITIALLY DEFERRED,
15667         creator        BIGINT   NOT NULL
15668                                 REFERENCES actor.usr (id)
15669                                 DEFERRABLE INITIALLY DEFERRED,
15670         editor         BIGINT   NOT NULL
15671                                 REFERENCES actor.usr (id)
15672                                 DEFERRABLE INITIALLY DEFERRED,
15673         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15674         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15675         name           TEXT     NOT NULL,
15676         -- columns above this point are attributes of the template itself
15677         -- columns after this point are attributes of the copy this template modifies/creates
15678         circ_lib       INT      REFERENCES actor.org_unit (id)
15679                                 DEFERRABLE INITIALLY DEFERRED,
15680         status         INT      REFERENCES config.copy_status (id)
15681                                 DEFERRABLE INITIALLY DEFERRED,
15682         location       INT      REFERENCES asset.copy_location (id)
15683                                 DEFERRABLE INITIALLY DEFERRED,
15684         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
15685                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
15686         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
15687                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
15688         age_protect    INT,
15689         circulate      BOOL,
15690         deposit        BOOL,
15691         ref            BOOL,
15692         holdable       BOOL,
15693         deposit_amount NUMERIC(6,2),
15694         price          NUMERIC(8,2),
15695         circ_modifier  TEXT,
15696         circ_as_type   TEXT,
15697         alert_message  TEXT,
15698         opac_visible   BOOL,
15699         floating       BOOL,
15700         mint_condition BOOL
15701 );
15702
15703 CREATE TABLE serial.subscription (
15704         id                     SERIAL       PRIMARY KEY,
15705         owning_lib             INT          NOT NULL DEFAULT 1
15706                                             REFERENCES actor.org_unit (id)
15707                                             ON DELETE SET NULL
15708                                             DEFERRABLE INITIALLY DEFERRED,
15709         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
15710         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
15711         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
15712                                             ON DELETE SET NULL
15713                                             DEFERRABLE INITIALLY DEFERRED,
15714         expected_date_offset   INTERVAL
15715         -- acquisitions/business-side tables link to here
15716 );
15717 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
15718 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
15719
15720 --at least one distribution per org_unit holding issues
15721 CREATE TABLE serial.distribution (
15722         id                    SERIAL  PRIMARY KEY,
15723         record_entry          BIGINT  REFERENCES serial.record_entry (id)
15724                                       ON DELETE SET NULL
15725                                       DEFERRABLE INITIALLY DEFERRED,
15726         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
15727                                           summary_method IS NULL
15728                                           OR summary_method IN ( 'add_to_sre',
15729                                           'merge_with_sre', 'use_sre_only',
15730                                           'use_sdist_only')),
15731         subscription          INT     NOT NULL
15732                                       REFERENCES serial.subscription (id)
15733                                                                   ON DELETE CASCADE
15734                                                                   DEFERRABLE INITIALLY DEFERRED,
15735         holding_lib           INT     NOT NULL
15736                                       REFERENCES actor.org_unit (id)
15737                                                                   DEFERRABLE INITIALLY DEFERRED,
15738         label                 TEXT    NOT NULL,
15739         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
15740                                       DEFERRABLE INITIALLY DEFERRED,
15741         receive_unit_template INT     REFERENCES asset.copy_template (id)
15742                                       DEFERRABLE INITIALLY DEFERRED,
15743         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
15744                                       DEFERRABLE INITIALLY DEFERRED,
15745         bind_unit_template    INT     REFERENCES asset.copy_template (id)
15746                                       DEFERRABLE INITIALLY DEFERRED,
15747         unit_label_prefix     TEXT,
15748         unit_label_suffix     TEXT
15749 );
15750 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
15751 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
15752
15753 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
15754
15755 CREATE TABLE serial.stream (
15756         id              SERIAL  PRIMARY KEY,
15757         distribution    INT     NOT NULL
15758                                 REFERENCES serial.distribution (id)
15759                                 ON DELETE CASCADE
15760                                 DEFERRABLE INITIALLY DEFERRED,
15761         routing_label   TEXT
15762 );
15763 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
15764
15765 CREATE UNIQUE INDEX label_once_per_dist
15766         ON serial.stream (distribution, routing_label)
15767         WHERE routing_label IS NOT NULL;
15768
15769 CREATE TABLE serial.routing_list_user (
15770         id             SERIAL       PRIMARY KEY,
15771         stream         INT          NOT NULL
15772                                     REFERENCES serial.stream
15773                                     ON DELETE CASCADE
15774                                     DEFERRABLE INITIALLY DEFERRED,
15775         pos            INT          NOT NULL DEFAULT 1,
15776         reader         INT          REFERENCES actor.usr
15777                                     ON DELETE CASCADE
15778                                     DEFERRABLE INITIALLY DEFERRED,
15779         department     TEXT,
15780         note           TEXT,
15781         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
15782         CONSTRAINT reader_or_dept CHECK
15783         (
15784             -- Recipient is a person or a department, but not both
15785                 (reader IS NOT NULL AND department IS NULL) OR
15786                 (reader IS NULL AND department IS NOT NULL)
15787         )
15788 );
15789 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
15790 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
15791
15792 CREATE TABLE serial.caption_and_pattern (
15793         id           SERIAL       PRIMARY KEY,
15794         subscription INT          NOT NULL REFERENCES serial.subscription (id)
15795                                   ON DELETE CASCADE
15796                                   DEFERRABLE INITIALLY DEFERRED,
15797         type         TEXT         NOT NULL
15798                                   CONSTRAINT cap_type CHECK ( type in
15799                                   ( 'basic', 'supplement', 'index' )),
15800         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
15801         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
15802         end_date     TIMESTAMP WITH TIME ZONE,
15803         active       BOOL         NOT NULL DEFAULT FALSE,
15804         pattern_code TEXT         NOT NULL,       -- must contain JSON
15805         enum_1       TEXT,
15806         enum_2       TEXT,
15807         enum_3       TEXT,
15808         enum_4       TEXT,
15809         enum_5       TEXT,
15810         enum_6       TEXT,
15811         chron_1      TEXT,
15812         chron_2      TEXT,
15813         chron_3      TEXT,
15814         chron_4      TEXT,
15815         chron_5      TEXT
15816 );
15817 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
15818
15819 CREATE TABLE serial.issuance (
15820         id              SERIAL    PRIMARY KEY,
15821         creator         INT       NOT NULL
15822                                   REFERENCES actor.usr (id)
15823                                                           DEFERRABLE INITIALLY DEFERRED,
15824         editor          INT       NOT NULL
15825                                   REFERENCES actor.usr (id)
15826                                   DEFERRABLE INITIALLY DEFERRED,
15827         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15828         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15829         subscription    INT       NOT NULL
15830                                   REFERENCES serial.subscription (id)
15831                                   ON DELETE CASCADE
15832                                   DEFERRABLE INITIALLY DEFERRED,
15833         label           TEXT,
15834         date_published  TIMESTAMP WITH TIME ZONE,
15835         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
15836                               DEFERRABLE INITIALLY DEFERRED,
15837         holding_code    TEXT,
15838         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
15839                                   (
15840                                       holding_type IS NULL
15841                                       OR holding_type IN ('basic','supplement','index')
15842                                   ),
15843         holding_link_id INT
15844         -- TODO: add columns for separate enumeration/chronology values
15845 );
15846 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
15847 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
15848 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
15849
15850 CREATE TABLE serial.unit (
15851         label           TEXT,
15852         label_sort_key  TEXT,
15853         contents        TEXT    NOT NULL
15854 ) INHERITS (asset.copy);
15855 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
15856 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
15857 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
15858 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
15859 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
15860
15861 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
15862
15863 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
15864
15865 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
15866
15867 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
15868
15869 CREATE TABLE serial.item (
15870         id              SERIAL  PRIMARY KEY,
15871         creator         INT     NOT NULL
15872                                 REFERENCES actor.usr (id)
15873                                 DEFERRABLE INITIALLY DEFERRED,
15874         editor          INT     NOT NULL
15875                                 REFERENCES actor.usr (id)
15876                                 DEFERRABLE INITIALLY DEFERRED,
15877         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15878         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15879         issuance        INT     NOT NULL
15880                                 REFERENCES serial.issuance (id)
15881                                 ON DELETE CASCADE
15882                                 DEFERRABLE INITIALLY DEFERRED,
15883         stream          INT     NOT NULL
15884                                 REFERENCES serial.stream (id)
15885                                 ON DELETE CASCADE
15886                                 DEFERRABLE INITIALLY DEFERRED,
15887         unit            INT     REFERENCES serial.unit (id)
15888                                 ON DELETE SET NULL
15889                                 DEFERRABLE INITIALLY DEFERRED,
15890         uri             INT     REFERENCES asset.uri (id)
15891                                 ON DELETE SET NULL
15892                                 DEFERRABLE INITIALLY DEFERRED,
15893         date_expected   TIMESTAMP WITH TIME ZONE,
15894         date_received   TIMESTAMP WITH TIME ZONE,
15895         status          TEXT    CONSTRAINT valid_status CHECK (
15896                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
15897                                'Expected', 'Not Held', 'Not Published', 'Received'))
15898                             DEFAULT 'Expected',
15899         shadowed        BOOL    NOT NULL DEFAULT FALSE
15900 );
15901 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
15902 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
15903 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
15904 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
15905 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
15906 CREATE INDEX serial_item_status_idx ON serial.item (status);
15907
15908 CREATE TABLE serial.item_note (
15909         id          SERIAL  PRIMARY KEY,
15910         item        INT     NOT NULL
15911                             REFERENCES serial.item (id)
15912                             ON DELETE CASCADE
15913                             DEFERRABLE INITIALLY DEFERRED,
15914         creator     INT     NOT NULL
15915                             REFERENCES actor.usr (id)
15916                             DEFERRABLE INITIALLY DEFERRED,
15917         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15918         pub         BOOL    NOT NULL    DEFAULT FALSE,
15919         title       TEXT    NOT NULL,
15920         value       TEXT    NOT NULL
15921 );
15922 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
15923
15924 CREATE TABLE serial.basic_summary (
15925         id                  SERIAL  PRIMARY KEY,
15926         distribution        INT     NOT NULL
15927                                     REFERENCES serial.distribution (id)
15928                                     ON DELETE CASCADE
15929                                     DEFERRABLE INITIALLY DEFERRED,
15930         generated_coverage  TEXT    NOT NULL,
15931         textual_holdings    TEXT,
15932         show_generated      BOOL    NOT NULL DEFAULT TRUE
15933 );
15934 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
15935
15936 CREATE TABLE serial.supplement_summary (
15937         id                  SERIAL  PRIMARY KEY,
15938         distribution        INT     NOT NULL
15939                                     REFERENCES serial.distribution (id)
15940                                     ON DELETE CASCADE
15941                                     DEFERRABLE INITIALLY DEFERRED,
15942         generated_coverage  TEXT    NOT NULL,
15943         textual_holdings    TEXT,
15944         show_generated      BOOL    NOT NULL DEFAULT TRUE
15945 );
15946 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
15947
15948 CREATE TABLE serial.index_summary (
15949         id                  SERIAL  PRIMARY KEY,
15950         distribution        INT     NOT NULL
15951                                     REFERENCES serial.distribution (id)
15952                                     ON DELETE CASCADE
15953                                     DEFERRABLE INITIALLY DEFERRED,
15954         generated_coverage  TEXT    NOT NULL,
15955         textual_holdings    TEXT,
15956         show_generated      BOOL    NOT NULL DEFAULT TRUE
15957 );
15958 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
15959
15960 -- 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.
15961
15962 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
15963 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
15964
15965 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
15966 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;
15967
15968 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
15969 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
15970
15971 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
15972 RETURNS INTEGER AS $$
15973 BEGIN
15974         RETURN EXTRACT( EPOCH FROM interval_val );
15975 END;
15976 $$ LANGUAGE plpgsql;
15977
15978 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
15979 RETURNS INTEGER AS $$
15980 BEGIN
15981         RETURN config.interval_to_seconds( interval_string::INTERVAL );
15982 END;
15983 $$ LANGUAGE plpgsql;
15984
15985 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
15986     'temp',
15987     oils_i18n_gettext(
15988         'temp',
15989         'Temporary bucket which gets deleted after use.',
15990         'cbrebt',
15991         'label'
15992     )
15993 );
15994
15995 -- 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.
15996
15997 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
15998 BEGIN
15999
16000     IF xml_is_well_formed(NEW.marc) THEN
16001         RETURN NEW;
16002     ELSE
16003         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16004     END IF;
16005     
16006 END;
16007 $func$ LANGUAGE PLPGSQL;
16008
16009 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();
16010
16011 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();
16012
16013 ALTER TABLE serial.record_entry
16014         ALTER COLUMN marc DROP NOT NULL;
16015
16016 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16017 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16018 <xsl:stylesheet
16019     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16020     xmlns:marc="http://www.loc.gov/MARC21/slim"
16021     version="1.0">
16022 <!--
16023 Copyright (C) 2010  Equinox Software, Inc.
16024 Galen Charlton <gmc@esilibrary.cOM.
16025
16026 This program is free software; you can redistribute it and/or
16027 modify it under the terms of the GNU General Public License
16028 as published by the Free Software Foundation; either version 2
16029 of the License, or (at your option) any later version.
16030
16031 This program is distributed in the hope that it will be useful,
16032 but WITHOUT ANY WARRANTY; without even the implied warranty of
16033 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16034 GNU General Public License for more details.
16035
16036 marc21_expand_880.xsl - stylesheet used during indexing to
16037                         map alternative graphical representations
16038                         of MARC fields stored in 880 fields
16039                         to the corresponding tag name and value.
16040
16041 For example, if a MARC record for a Chinese book has
16042
16043 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16044 880.00 $6 245-01/$1 $a八十三年短篇小說選
16045
16046 this stylesheet will transform it to the equivalent of
16047
16048 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16049 245.00 $6 245-01/$1 $a八十三年短篇小說選
16050
16051 -->
16052     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16053
16054     <xsl:template match="@*|node()">
16055         <xsl:copy>
16056             <xsl:apply-templates select="@*|node()"/>
16057         </xsl:copy>
16058     </xsl:template>
16059
16060     <xsl:template match="//marc:datafield[@tag='880']">
16061         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16062             <marc:datafield>
16063                 <xsl:attribute name="tag">
16064                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16065                 </xsl:attribute>
16066                 <xsl:attribute name="ind1">
16067                     <xsl:value-of select="@ind1" />
16068                 </xsl:attribute>
16069                 <xsl:attribute name="ind2">
16070                     <xsl:value-of select="@ind2" />
16071                 </xsl:attribute>
16072                 <xsl:apply-templates />
16073             </marc:datafield>
16074         </xsl:if>
16075     </xsl:template>
16076     
16077 </xsl:stylesheet>$$);
16078
16079 -- Splitting the ingest trigger up into little bits
16080
16081 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16082     flag INTEGER PRIMARY KEY
16083 ) ON COMMIT DROP;
16084 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16085
16086 -- cause failure if either of the tables we want to drop have rows
16087 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16088 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16089
16090 DROP TABLE IF EXISTS asset.copy_transparency_map;
16091 DROP TABLE IF EXISTS asset.copy_transparency;
16092
16093 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16094
16095 -- We won't necessarily use all of these, but they are here for completeness.
16096 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16097 -- Values are the EDI code value + 1000
16098
16099 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16100 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16101 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16102 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16103 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16104 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16105 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16106 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16107 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16108 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16109 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16110 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16111 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16112 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16113 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16114 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16115 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16116 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16117 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16118 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16119 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16120 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16121 ('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).'),
16122 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16123 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16124 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16125 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16126 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16127 ('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.'),
16128 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16129 ('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.'),
16130 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16131 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16132 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16133 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16134 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16135 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16136 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16137 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16138 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16139 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16140 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16141 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16142 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16143 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16144 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16145 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16146 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16147 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16148 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16149 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16150 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16151 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16152 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16153 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16154 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16155 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16156 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16157 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16158 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16159 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16160 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16161 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16162 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16163 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16164 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16165 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16166 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16167 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16168 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16169 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16170 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16171 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16172 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16173 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16174 ('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).'),
16175 ('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).'),
16176 ('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).'),
16177 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16178 ('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).'),
16179 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16180 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16181 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16182 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16183 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16184 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16185 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16186 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16187 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16188 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16189 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16190 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16191 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16192 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16193 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16194 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16195 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16196 ('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.'),
16197 ('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.'),
16198 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16199 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16200 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16201 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16202 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16203 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16204 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16205 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16206 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16207 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16208 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16209 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16210 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16211 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16212 ('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.'),
16213 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16214 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16215
16216 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16217     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16218  
16219 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16220  
16221 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16222         'Remove Parenthesized Substring',
16223         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16224         'remove_paren_substring',
16225         0
16226 );
16227
16228 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16229         'Trim Surrounding Space',
16230         'Trim leading and trailing spaces from extracted text.',
16231         'btrim',
16232         0
16233 );
16234
16235 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16236     SELECT  m.id,
16237             i.id,
16238             -2
16239       FROM  config.metabib_field m,
16240             config.index_normalizer i
16241       WHERE i.func IN ('remove_paren_substring')
16242             AND m.id IN (26);
16243
16244 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16245     SELECT  m.id,
16246             i.id,
16247             -1
16248       FROM  config.metabib_field m,
16249             config.index_normalizer i
16250       WHERE i.func IN ('btrim')
16251             AND m.id IN (26);
16252
16253 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16254 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16255 DECLARE
16256     dyn_profile     vandelay.compile_profile%ROWTYPE;
16257     replace_rule    TEXT;
16258     tmp_marc        TEXT;
16259     trgt_marc        TEXT;
16260     tmpl_marc        TEXT;
16261     match_count     INT;
16262 BEGIN
16263
16264     IF target_marc IS NULL OR template_marc IS NULL THEN
16265         -- RAISE NOTICE 'no marc for target or template record';
16266         RETURN NULL;
16267     END IF;
16268
16269     dyn_profile := vandelay.compile_profile( template_marc );
16270
16271     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16272         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16273         RETURN NULL;
16274     END IF;
16275
16276     IF dyn_profile.replace_rule <> '' THEN
16277         trgt_marc = target_marc;
16278         tmpl_marc = template_marc;
16279         replace_rule = dyn_profile.replace_rule;
16280     ELSE
16281         tmp_marc = target_marc;
16282         trgt_marc = template_marc;
16283         tmpl_marc = tmp_marc;
16284         replace_rule = dyn_profile.preserve_rule;
16285     END IF;
16286
16287     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16288
16289 END;
16290 $$ LANGUAGE PLPGSQL;
16291
16292 -- Function to generate an ephemeral overlay template from an authority record
16293 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16294
16295     use MARC::Record;
16296     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16297
16298     my $xml = shift;
16299     my $r = MARC::Record->new_from_xml( $xml );
16300
16301     return undef unless ($r);
16302
16303     my $id = shift() || $r->subfield( '901' => 'c' );
16304     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16305     return undef unless ($id); # We need an ID!
16306
16307     my $tmpl = MARC::Record->new();
16308
16309     my @rule_fields;
16310     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16311
16312         my $tag = $field->tag;
16313         my $i1 = $field->indicator(1);
16314         my $i2 = $field->indicator(2);
16315         my $sf = join '', map { $_->[0] } $field->subfields;
16316         my @data = map { @$_ } $field->subfields;
16317
16318         my @replace_them;
16319
16320         # Map the authority field to bib fields it can control.
16321         if ($tag >= 100 and $tag <= 111) {       # names
16322             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16323         } elsif ($tag eq '130') {                # uniform title
16324             @replace_them = qw/130 240 440 730 830/;
16325         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16326             @replace_them = ($tag + 500);
16327         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16328             @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/;
16329         } else {
16330             next;
16331         }
16332
16333         # Dummy up the bib-side data
16334         $tmpl->append_fields(
16335             map {
16336                 MARC::Field->new( $_, $i1, $i2, @data )
16337             } @replace_them
16338         );
16339
16340         # Construct some 'replace' rules
16341         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16342     }
16343
16344     # Insert the replace rules into the template
16345     $tmpl->append_fields(
16346         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16347     );
16348
16349     $xml = $tmpl->as_xml_record;
16350     $xml =~ s/^<\?.+?\?>$//mo;
16351     $xml =~ s/\n//sgo;
16352     $xml =~ s/>\s+</></sgo;
16353
16354     return $xml;
16355
16356 $func$ LANGUAGE PLPERLU;
16357
16358 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16359     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16360 $func$ LANGUAGE SQL;
16361
16362 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16363     SELECT authority.generate_overlay_template( $1, NULL );
16364 $func$ LANGUAGE SQL;
16365
16366 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16367 DELETE FROM config.metabib_field WHERE id = 26;
16368
16369 -- Making this a global_flag (UI accessible) instead of an internal_flag
16370 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16371     VALUES (
16372         'ingest.disable_authority_linking',
16373         oils_i18n_gettext(
16374             'ingest.disable_authority_linking',
16375             'Authority Automation: Disable bib-authority link tracking',
16376             'cgf', 
16377             'label'
16378         )
16379     );
16380 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16381 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16382
16383 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16384     VALUES (
16385         'ingest.disable_authority_auto_update',
16386         oils_i18n_gettext(
16387             'ingest.disable_authority_auto_update',
16388             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16389             'cgf', 
16390             'label'
16391         )
16392     );
16393
16394 -- Enable automated ingest of authority records; just insert the row into
16395 -- authority.record_entry and authority.full_rec will automatically be populated
16396
16397 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16398     UPDATE  biblio.record_entry
16399       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16400       WHERE id = $2;
16401     SELECT $1;
16402 $func$ LANGUAGE SQL;
16403
16404 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16405     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16406 $func$ LANGUAGE SQL;
16407
16408 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16409
16410 use MARC::Record;
16411 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16412
16413 my $xml = shift;
16414 my $r = MARC::Record->new_from_xml( $xml );
16415
16416 return_next( { tag => 'LDR', value => $r->leader } );
16417
16418 for my $f ( $r->fields ) {
16419     if ($f->is_control_field) {
16420         return_next({ tag => $f->tag, value => $f->data });
16421     } else {
16422         for my $s ($f->subfields) {
16423             return_next({
16424                 tag      => $f->tag,
16425                 ind1     => $f->indicator(1),
16426                 ind2     => $f->indicator(2),
16427                 subfield => $s->[0],
16428                 value    => $s->[1]
16429             });
16430
16431         }
16432     }
16433 }
16434
16435 return undef;
16436
16437 $func$ LANGUAGE PLPERLU;
16438
16439 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16440 DECLARE
16441     auth    authority.record_entry%ROWTYPE;
16442     output    authority.full_rec%ROWTYPE;
16443     field    RECORD;
16444 BEGIN
16445     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16446
16447     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16448         output.record := rid;
16449         output.ind1 := field.ind1;
16450         output.ind2 := field.ind2;
16451         output.tag := field.tag;
16452         output.subfield := field.subfield;
16453         IF field.subfield IS NOT NULL THEN
16454             output.value := naco_normalize(field.value, field.subfield);
16455         ELSE
16456             output.value := field.value;
16457         END IF;
16458
16459         CONTINUE WHEN output.value IS NULL;
16460
16461         RETURN NEXT output;
16462     END LOOP;
16463 END;
16464 $func$ LANGUAGE PLPGSQL;
16465
16466 -- authority.rec_descriptor appears to be unused currently
16467 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16468 BEGIN
16469     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16470 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16471 --        SELECT  auth_id, ;
16472
16473     RETURN;
16474 END;
16475 $func$ LANGUAGE PLPGSQL;
16476
16477 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16478 BEGIN
16479     DELETE FROM authority.full_rec WHERE record = auth_id;
16480     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16481         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16482
16483     RETURN;
16484 END;
16485 $func$ LANGUAGE PLPGSQL;
16486
16487 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16488 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16489 BEGIN
16490
16491     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16492         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16493           -- Should remove matching $0 from controlled fields at the same time?
16494         RETURN NEW; -- and we're done
16495     END IF;
16496
16497     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16498         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16499
16500         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16501             RETURN NEW;
16502         END IF;
16503     END IF;
16504
16505     -- Flatten and insert the afr data
16506     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16507     IF NOT FOUND THEN
16508         PERFORM authority.reingest_authority_full_rec(NEW.id);
16509 -- authority.rec_descriptor is not currently used
16510 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16511 --        IF NOT FOUND THEN
16512 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16513 --        END IF;
16514     END IF;
16515
16516     RETURN NEW;
16517 END;
16518 $func$ LANGUAGE PLPGSQL;
16519
16520 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 ();
16521
16522 -- Some records manage to get XML namespace declarations into each element,
16523 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16524 -- This broke the old maintain_901(), so we'll make the regex more robust
16525
16526 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16527 BEGIN
16528     -- Remove any existing 901 fields before we insert the authoritative one
16529     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16530     IF TG_TABLE_SCHEMA = 'biblio' THEN
16531         NEW.marc := REGEXP_REPLACE(
16532             NEW.marc,
16533             E'(</(?:[^:]*?:)?record>)',
16534             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16535                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16536                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16537                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16538                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16539                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16540                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16541              E'</datafield>\\1'
16542         );
16543     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16544         NEW.marc := REGEXP_REPLACE(
16545             NEW.marc,
16546             E'(</(?:[^:]*?:)?record>)',
16547             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16548                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16549                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16550              E'</datafield>\\1'
16551         );
16552     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16553         NEW.marc := REGEXP_REPLACE(
16554             NEW.marc,
16555             E'(</(?:[^:]*?:)?record>)',
16556             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16557                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16558                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16559                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16560                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16561              E'</datafield>\\1'
16562         );
16563     ELSE
16564         NEW.marc := REGEXP_REPLACE(
16565             NEW.marc,
16566             E'(</(?:[^:]*?:)?record>)',
16567             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16568                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16569                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16570              E'</datafield>\\1'
16571         );
16572     END IF;
16573
16574     RETURN NEW;
16575 END;
16576 $func$ LANGUAGE PLPGSQL;
16577
16578 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16579 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16580 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16581  
16582 -- In booking, elbow room defines:
16583 --  a) how far in the future you must make a reservation on a given item if
16584 --      that item will have to transit somewhere to fulfill the reservation.
16585 --  b) how soon a reservation must be starting for the reserved item to
16586 --      be op-captured by the checkin interface.
16587
16588 -- We don't want to clobber any default_elbow room at any level:
16589
16590 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16591 DECLARE
16592     existing    actor.org_unit_setting%ROWTYPE;
16593 BEGIN
16594     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16595     IF NOT FOUND THEN
16596         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16597             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16598             'circ.booking_reservation.default_elbow_room',
16599             '"1 day"'
16600         );
16601         RETURN 1;
16602     END IF;
16603     RETURN 0;
16604 END;
16605 $$ LANGUAGE plpgsql;
16606
16607 SELECT pg_temp.default_elbow();
16608
16609 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16610
16611 -- returns the distinct set of target copy IDs from a user's visible circulation history
16612 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16613     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16614 $$ LANGUAGE SQL;
16615
16616 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
16617 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
16618 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
16619 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
16620
16621 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
16622
16623 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
16624 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
16625
16626 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16627     VALUES (
16628         'cat.maintain_control_numbers',
16629         oils_i18n_gettext(
16630             'cat.maintain_control_numbers',
16631             'Cat: Maintain 001/003/035 according to the MARC21 specification',
16632             'cgf', 
16633             'label'
16634         )
16635     );
16636
16637 INSERT INTO config.global_flag (name, label, enabled)
16638     VALUES (
16639         'circ.holds.empty_issuance_ok',
16640         oils_i18n_gettext(
16641             'circ.holds.empty_issuance_ok',
16642             'Holds: Allow holds on empty issuances',
16643             'cgf',
16644             'label'
16645         ),
16646         TRUE
16647     );
16648
16649 INSERT INTO config.global_flag (name, label, enabled)
16650     VALUES (
16651         'circ.holds.usr_not_requestor',
16652         oils_i18n_gettext(
16653             'circ.holds.usr_not_requestor',
16654             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
16655             'cgf',
16656             'label'
16657         ),
16658         TRUE
16659     );
16660
16661 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
16662 use strict;
16663 use MARC::Record;
16664 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16665 use Encode;
16666 use Unicode::Normalize;
16667
16668 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
16669 my $schema = $_TD->{table_schema};
16670 my $rec_id = $_TD->{new}{id};
16671
16672 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
16673 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
16674 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
16675     return;
16676 }
16677
16678 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
16679 my $ou_cni = 'EVRGRN';
16680
16681 my $owner;
16682 if ($schema eq 'serial') {
16683     $owner = $_TD->{new}{owning_lib};
16684 } else {
16685     # are.owner and bre.owner can be null, so fall back to the consortial setting
16686     $owner = $_TD->{new}{owner} || 1;
16687 }
16688
16689 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
16690 if ($ous_rv->{processed}) {
16691     $ou_cni = $ous_rv->{rows}[0]->{value};
16692     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
16693 } else {
16694     # Fall back to the shortname of the OU if there was no OU setting
16695     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
16696     if ($ous_rv->{processed}) {
16697         $ou_cni = $ous_rv->{rows}[0]->{shortname};
16698     }
16699 }
16700
16701 my ($create, $munge) = (0, 0);
16702 my ($orig_001, $orig_003) = ('', '');
16703
16704 # Incoming MARC records may have multiple 001s or 003s, despite the spec
16705 my @control_ids = $record->field('003');
16706 my @scns = $record->field('035');
16707
16708 foreach my $id_field ('001', '003') {
16709     my $spec_value;
16710     my @controls = $record->field($id_field);
16711
16712     if ($id_field eq '001') {
16713         $spec_value = $rec_id;
16714     } else {
16715         $spec_value = $ou_cni;
16716     }
16717
16718     # Create the 001/003 if none exist
16719     if (scalar(@controls) == 0) {
16720         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
16721         $create = 1;
16722     } elsif (scalar(@controls) > 1) {
16723         # Do we already have the right 001/003 value in the existing set?
16724         unless (grep $_->data() eq $spec_value, @controls) {
16725             $munge = 1;
16726         }
16727
16728         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
16729         foreach my $control (@controls) {
16730             unless ($control->data() eq $spec_value) {
16731                 $record->delete_field($control);
16732             }
16733         }
16734     } else {
16735         # Only one field; check to see if we need to munge it
16736         unless (grep $_->data() eq $spec_value, @controls) {
16737             $munge = 1;
16738         }
16739     }
16740 }
16741
16742 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
16743 if ($munge) {
16744     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
16745
16746     # Do not create duplicate 035 fields
16747     unless (grep $_->subfield('a') eq $scn, @scns) {
16748         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
16749     }
16750 }
16751
16752 # Set the 001/003 and update the MARC
16753 if ($create or $munge) {
16754     $record->field('001')->data($rec_id);
16755     $record->field('003')->data($ou_cni);
16756
16757     my $xml = $record->as_xml_record();
16758     $xml =~ s/\n//sgo;
16759     $xml =~ s/^<\?xml.+\?\s*>//go;
16760     $xml =~ s/>\s+</></go;
16761     $xml =~ s/\p{Cc}//go;
16762
16763     # Embed a version of OpenILS::Application::AppUtils->entityize()
16764     # to avoid having to set PERL5LIB for PostgreSQL as well
16765
16766     # If we are going to convert non-ASCII characters to XML entities,
16767     # we had better be dealing with a UTF8 string to begin with
16768     $xml = decode_utf8($xml);
16769
16770     $xml = NFC($xml);
16771
16772     # Convert raw ampersands to entities
16773     $xml =~ s/&(?!\S+;)/&amp;/gso;
16774
16775     # Convert Unicode characters to entities
16776     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
16777
16778     $xml =~ s/[\x00-\x1f]//go;
16779     $_TD->{new}{marc} = $xml;
16780
16781     return "MODIFY";
16782 }
16783
16784 return;
16785 $func$ LANGUAGE PLPERLU;
16786
16787 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16788 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16789 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16790
16791 INSERT INTO metabib.facet_entry (source, field, value)
16792     SELECT source, field, value FROM (
16793         SELECT * FROM metabib.author_field_entry
16794             UNION ALL
16795         SELECT * FROM metabib.keyword_field_entry
16796             UNION ALL
16797         SELECT * FROM metabib.identifier_field_entry
16798             UNION ALL
16799         SELECT * FROM metabib.title_field_entry
16800             UNION ALL
16801         SELECT * FROM metabib.subject_field_entry
16802             UNION ALL
16803         SELECT * FROM metabib.series_field_entry
16804         )x
16805     WHERE x.index_vector = '';
16806         
16807 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
16808 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
16809 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
16810 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
16811 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
16812 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
16813
16814 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
16815 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
16816 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
16817
16818 -- copy OPAC visibility materialized view
16819 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
16820
16821     TRUNCATE TABLE asset.opac_visible_copies;
16822
16823     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16824     SELECT  cp.id, cp.circ_lib, cn.record
16825     FROM  asset.copy cp
16826         JOIN asset.call_number cn ON (cn.id = cp.call_number)
16827         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
16828         JOIN asset.copy_location cl ON (cp.location = cl.id)
16829         JOIN config.copy_status cs ON (cp.status = cs.id)
16830         JOIN biblio.record_entry b ON (cn.record = b.id)
16831     WHERE NOT cp.deleted
16832         AND NOT cn.deleted
16833         AND NOT b.deleted
16834         AND cs.opac_visible
16835         AND cl.opac_visible
16836         AND cp.opac_visible
16837         AND a.opac_visible;
16838
16839 $$ LANGUAGE SQL;
16840 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
16841 Rebuild the copy OPAC visibility cache.  Useful during migrations.
16842 $$;
16843
16844 -- and actually populate the table
16845 SELECT asset.refresh_opac_visible_copies_mat_view();
16846
16847 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
16848 DECLARE
16849     add_query       TEXT;
16850     remove_query    TEXT;
16851     do_add          BOOLEAN := false;
16852     do_remove       BOOLEAN := false;
16853 BEGIN
16854     add_query := $$
16855             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16856                 SELECT  cp.id, cp.circ_lib, cn.record
16857                   FROM  asset.copy cp
16858                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
16859                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
16860                         JOIN asset.copy_location cl ON (cp.location = cl.id)
16861                         JOIN config.copy_status cs ON (cp.status = cs.id)
16862                         JOIN biblio.record_entry b ON (cn.record = b.id)
16863                   WHERE NOT cp.deleted
16864                         AND NOT cn.deleted
16865                         AND NOT b.deleted
16866                         AND cs.opac_visible
16867                         AND cl.opac_visible
16868                         AND cp.opac_visible
16869                         AND a.opac_visible
16870     $$;
16871  
16872     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
16873
16874     IF TG_OP = 'INSERT' THEN
16875
16876         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
16877             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
16878             EXECUTE add_query;
16879         END IF;
16880
16881         RETURN NEW;
16882
16883     END IF;
16884
16885     -- handle items first, since with circulation activity
16886     -- their statuses change frequently
16887     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
16888
16889         IF OLD.location    <> NEW.location OR
16890            OLD.call_number <> NEW.call_number OR
16891            OLD.status      <> NEW.status OR
16892            OLD.circ_lib    <> NEW.circ_lib THEN
16893             -- any of these could change visibility, but
16894             -- we'll save some queries and not try to calculate
16895             -- the change directly
16896             do_remove := true;
16897             do_add := true;
16898         ELSE
16899
16900             IF OLD.deleted <> NEW.deleted THEN
16901                 IF NEW.deleted THEN
16902                     do_remove := true;
16903                 ELSE
16904                     do_add := true;
16905                 END IF;
16906             END IF;
16907
16908             IF OLD.opac_visible <> NEW.opac_visible THEN
16909                 IF OLD.opac_visible THEN
16910                     do_remove := true;
16911                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
16912                                         -- is also marked opac_visible
16913                     do_add := true;
16914                 END IF;
16915             END IF;
16916
16917         END IF;
16918
16919         IF do_remove THEN
16920             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
16921         END IF;
16922         IF do_add THEN
16923             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
16924             EXECUTE add_query;
16925         END IF;
16926
16927         RETURN NEW;
16928
16929     END IF;
16930
16931     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
16932  
16933         IF OLD.deleted AND NEW.deleted THEN -- do nothing
16934
16935             RETURN NEW;
16936  
16937         ELSIF NEW.deleted THEN -- remove rows
16938  
16939             IF TG_TABLE_NAME = 'call_number' THEN
16940                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
16941             ELSIF TG_TABLE_NAME = 'record_entry' THEN
16942                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
16943             END IF;
16944  
16945             RETURN NEW;
16946  
16947         ELSIF OLD.deleted THEN -- add rows
16948  
16949             IF TG_TABLE_NAME IN ('copy','unit') THEN
16950                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
16951             ELSIF TG_TABLE_NAME = 'call_number' THEN
16952                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
16953             ELSIF TG_TABLE_NAME = 'record_entry' THEN
16954                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
16955             END IF;
16956  
16957             EXECUTE add_query;
16958             RETURN NEW;
16959  
16960         END IF;
16961  
16962     END IF;
16963
16964     IF TG_TABLE_NAME = 'call_number' THEN
16965
16966         IF OLD.record <> NEW.record THEN
16967             -- call number is linked to different bib
16968             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
16969             EXECUTE remove_query;
16970             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
16971             EXECUTE add_query;
16972         END IF;
16973
16974         RETURN NEW;
16975
16976     END IF;
16977
16978     IF TG_TABLE_NAME IN ('record_entry') THEN
16979         RETURN NEW; -- don't have 'opac_visible'
16980     END IF;
16981
16982     -- actor.org_unit, asset.copy_location, asset.copy_status
16983     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
16984
16985         RETURN NEW;
16986
16987     ELSIF NEW.opac_visible THEN -- add rows
16988
16989         IF TG_TABLE_NAME = 'org_unit' THEN
16990             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
16991         ELSIF TG_TABLE_NAME = 'copy_location' THEN
16992             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
16993         ELSIF TG_TABLE_NAME = 'copy_status' THEN
16994             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
16995         END IF;
16996  
16997         EXECUTE add_query;
16998  
16999     ELSE -- delete rows
17000
17001         IF TG_TABLE_NAME = 'org_unit' THEN
17002             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17003         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17004             remove_query := remove_query || 'location = ' || NEW.id || ');';
17005         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17006             remove_query := remove_query || 'status = ' || NEW.id || ');';
17007         END IF;
17008  
17009         EXECUTE remove_query;
17010  
17011     END IF;
17012  
17013     RETURN NEW;
17014 END;
17015 $func$ LANGUAGE PLPGSQL;
17016 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17017 Trigger function to update the copy OPAC visiblity cache.
17018 $$;
17019 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();
17020 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17021 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();
17022 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();
17023 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17024 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();
17025 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();
17026
17027 -- must create this rule explicitly; it is not inherited from asset.copy
17028 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;
17029
17030 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);
17031
17032 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17033 DECLARE
17034     moved_objects INT := 0;
17035     bib_id        INT := 0;
17036     bib_rec       biblio.record_entry%ROWTYPE;
17037     auth_link     authority.bib_linking%ROWTYPE;
17038 BEGIN
17039
17040     -- 1. Make source_record MARC a copy of the target_record to get auto-sync in linked bib records
17041     UPDATE authority.record_entry
17042       SET marc = (
17043         SELECT marc
17044           FROM authority.record_entry
17045           WHERE id = target_record
17046       )
17047       WHERE id = source_record;
17048
17049     -- 2. Update all bib records with the ID from target_record in their $0
17050     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre 
17051       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17052       WHERE abl.authority = target_record LOOP
17053
17054         UPDATE biblio.record_entry
17055           SET marc = REGEXP_REPLACE(marc, 
17056             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17057             E'\\1' || target_record || '<', 'g')
17058           WHERE id = bib_rec.id;
17059
17060           moved_objects := moved_objects + 1;
17061     END LOOP;
17062
17063     -- 3. "Delete" source_record
17064     DELETE FROM authority.record_entry
17065       WHERE id = source_record;
17066
17067     RETURN moved_objects;
17068 END;
17069 $func$ LANGUAGE plpgsql;
17070
17071 -- serial.record_entry already had an owner column spelled "owning_lib"
17072 -- Adjust the table and affected functions accordingly
17073
17074 ALTER TABLE serial.record_entry DROP COLUMN owner;
17075
17076 CREATE TABLE actor.usr_saved_search (
17077     id              SERIAL          PRIMARY KEY,
17078         owner           INT             NOT NULL REFERENCES actor.usr (id)
17079                                         ON DELETE CASCADE
17080                                         DEFERRABLE INITIALLY DEFERRED,
17081         name            TEXT            NOT NULL,
17082         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17083         query_text      TEXT            NOT NULL,
17084         query_type      TEXT            NOT NULL
17085                                         CONSTRAINT valid_query_text CHECK (
17086                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17087                                         -- we may add other types someday
17088         target          TEXT            NOT NULL
17089                                         CONSTRAINT valid_target CHECK (
17090                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17091         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17092 );
17093
17094 -- Apply Dan Wells' changes to the serial schema, from the
17095 -- seials-integration branch
17096
17097 CREATE TABLE serial.subscription_note (
17098         id           SERIAL PRIMARY KEY,
17099         subscription INT    NOT NULL
17100                             REFERENCES serial.subscription (id)
17101                             ON DELETE CASCADE
17102                             DEFERRABLE INITIALLY DEFERRED,
17103         creator      INT    NOT NULL
17104                             REFERENCES actor.usr (id)
17105                             DEFERRABLE INITIALLY DEFERRED,
17106         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17107         pub          BOOL   NOT NULL DEFAULT FALSE,
17108         title        TEXT   NOT NULL,
17109         value        TEXT   NOT NULL
17110 );
17111 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17112
17113 CREATE TABLE serial.distribution_note (
17114         id           SERIAL PRIMARY KEY,
17115         distribution INT    NOT NULL
17116                             REFERENCES serial.distribution (id)
17117                             ON DELETE CASCADE
17118                             DEFERRABLE INITIALLY DEFERRED,
17119         creator      INT    NOT NULL
17120                             REFERENCES actor.usr (id)
17121                             DEFERRABLE INITIALLY DEFERRED,
17122         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17123         pub          BOOL   NOT NULL DEFAULT FALSE,
17124         title        TEXT   NOT NULL,
17125         value        TEXT   NOT NULL
17126 );
17127 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17128
17129 ------- Begin surgery on serial.unit
17130
17131 ALTER TABLE serial.unit
17132         DROP COLUMN label;
17133
17134 ALTER TABLE serial.unit
17135         RENAME COLUMN label_sort_key TO sort_key;
17136
17137 ALTER TABLE serial.unit
17138         RENAME COLUMN contents TO detailed_contents;
17139
17140 ALTER TABLE serial.unit
17141         ADD COLUMN summary_contents TEXT;
17142
17143 UPDATE serial.unit
17144 SET summary_contents = detailed_contents;
17145
17146 ALTER TABLE serial.unit
17147         ALTER column summary_contents SET NOT NULL;
17148
17149 ------- End surgery on serial.unit
17150
17151 -- 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' );
17152
17153 -- Now rebuild the constraints dropped via cascade.
17154 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17155 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17156 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17157
17158 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17159
17160 DELETE FROM config.metabib_field_index_norm_map
17161     WHERE norm IN (
17162         SELECT id 
17163             FROM config.index_normalizer
17164             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17165     )
17166     AND field = 18
17167 ;
17168
17169 -- We won't necessarily use all of these, but they are here for completeness.
17170 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17171 -- Values are the EDI code value + 1200
17172
17173 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17174 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17175 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17176 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17177 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17178 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17179 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17180 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17181 (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.'),
17182 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17183 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17184 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17185 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17186 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17187 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17188 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17189 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17190 (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.'),
17191 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17192 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17193 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17194 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17195 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17196 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17197 (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.'),
17198 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17199 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17200 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17201 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17202 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17203 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17204 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17205 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17206 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17207 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17208 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17209 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17210 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17211 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17212 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17213 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17214 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17215 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17216 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17217 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17218 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17219 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17220 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17221 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17222 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17223 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17224 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17225 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17226 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17227 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17228 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17229 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17230 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17231 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17232 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17233 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17234 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17235 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17236 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17237 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17238 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17239 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17240 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17241 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17242 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17243 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17244 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17245 (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.'),
17246 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17247 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17248 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17249 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17250 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17251 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17252 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17253 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17254 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17255 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17256 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17257 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17258 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17259 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17260 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17261 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17262 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17263 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17264 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17265 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17266 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17267 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17268 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17269 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17270 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17271 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17272 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17273 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17274 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17275 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17276 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17277 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17278 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17279 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17280 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17281 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17282 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17283 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17284 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17285 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17286 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17287 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17288 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17289 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17290 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17291 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17292 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17293 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17294 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17295 (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.'),
17296 (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.'),
17297 (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.'),
17298 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17299 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17300 (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.'),
17301 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17302 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17303 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17304 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17305 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17306 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17307 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17308 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17309 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17310 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17311 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17312 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17313 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17314 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17315 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17316 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17317 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17318 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17319 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17320 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17321 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17322 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17323 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17324 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17325 (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.'),
17326 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17327 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17328 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17329 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17330 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17331 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17332 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17333 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17334 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17335 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17336 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17337 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17338 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17339 (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.'),
17340 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17341 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17342 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17343 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17344 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17345 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17346 (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.'),
17347 (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.'),
17348 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17349 (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.'),
17350 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17351 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17352 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17353 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17354 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17355 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17356 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17357 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17358 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17359 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17360 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17361 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17362 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17363 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17364 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17365 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17366 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17367 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17368 (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.'),
17369 (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.'),
17370 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17371 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17372 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17373 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17374 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17375 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17376 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17377 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17378 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17379 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17380 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17381 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17382 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17383 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17384 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17385 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17386 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17387 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17388 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17389 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17390 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17391 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17392 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17393 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17394 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17395 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17396 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17397 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17398 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17399 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17400 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17401 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17402 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17403 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17404 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17405 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17406 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17407 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17408 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17409 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17410 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17411 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17412 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17413 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17414 (1, 't', 1442, 'Number of months', 'The number of months.'),
17415 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17416 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17417 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17418 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17419 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17420 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17421 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17422 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17423 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17424 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17425 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17426 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17427 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17428 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17429 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17430 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17431 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17432 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17433 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17434 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17435 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17436 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17437 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17438 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17439 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17440 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17441 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17442 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17443 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17444 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17445 (1, 't', 1473, 'Agents', 'The number of agents.'),
17446 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17447 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17448 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17449 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17450 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17451 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17452 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17453 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17454 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17455 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17456 (1, 't', 1484, 'Departments', 'The number of departments.'),
17457 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17458 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17459 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17460 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17461 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17462 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17463 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17464 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17465 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17466 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17467 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17468 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17469 (1, 't', 1497, 'Executives', 'The number of executives.'),
17470 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17471 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17472 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17473 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17474 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17475 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17476 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17477 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17478 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17479 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17480 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17481 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17482 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17483 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17484 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17485 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17486 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17487 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17488 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17489 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17490 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17491 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17492 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17493 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17494 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17495 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17496 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17497 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17498 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17499 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17500 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17501 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17502 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17503 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17504 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17505 (1, 't', 1533, 'Seats',        'The number of seats.'),
17506 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17507 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17508 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17509 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17510 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17511 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17512 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17513 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17514 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17515 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17516 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17517 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17518 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17519 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17520 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17521 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17522 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17523 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17524 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17525 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17526 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17527 (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.'),
17528 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17529 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17530 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17531 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17532 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17533 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17534 (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.'),
17535 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17536 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17537 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17538 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17539 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17540 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17541 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17542 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17543 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17544 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17545 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17546 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17547 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17548 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17549 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17550 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17551 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17552 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17553 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17554 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17555 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17556 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17557 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17558 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17559 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17560 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17561 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17562 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17563 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17564 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17565 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17566 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17567 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17568 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17569 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17570 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17571 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17572 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17573 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17574 (1, 't', 1602, 'Patients',         'Number of patients.'),
17575 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17576 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17577 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17578 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17579 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17580 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17581 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17582 (1, 't', 1610, 'Operators',        'Number of operators.'),
17583 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17584 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17585 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17586 (1, 't', 1614, 'Machines',         'Number of machines.'),
17587 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17588 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17589 (1, 't', 1617, 'Directors',        'Number of directors.'),
17590 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17591 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17592 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17593 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17594 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17595 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17596 (1, 't', 1624, 'Beds', 'Number of beds.'),
17597 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17598 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17599 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17600 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
17601 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
17602 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
17603 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
17604 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
17605 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
17606 (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.'),
17607 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
17608 (1, 't', 1636, 'Professor', 'The number of professors.'),
17609 (1, 't', 1637, 'Seller',    'The number of sellers.'),
17610 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
17611 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
17612 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
17613 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
17614 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
17615 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
17616 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
17617 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
17618 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
17619 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
17620 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
17621 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
17622 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
17623 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
17624 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
17625 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
17626 (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.'),
17627 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
17628 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
17629 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
17630 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
17631 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
17632 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
17633 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
17634 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
17635 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
17636 (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.'),
17637 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
17638 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
17639 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
17640 (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.'),
17641 (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.'),
17642 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
17643 (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.'),
17644 (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.'),
17645 (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.'),
17646 (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.'),
17647 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
17648 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
17649 (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.'),
17650 (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.'),
17651 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
17652 (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.'),
17653 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
17654 (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.'),
17655 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
17656 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
17657 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
17658 (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).'),
17659 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
17660 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
17661 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
17662 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
17663 ;
17664 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
17665
17666 CREATE TABLE acq.serial_claim (
17667     id     SERIAL           PRIMARY KEY,
17668     type   INT              NOT NULL REFERENCES acq.claim_type
17669                                      DEFERRABLE INITIALLY DEFERRED,
17670     item    BIGINT          NOT NULL REFERENCES serial.item
17671                                      DEFERRABLE INITIALLY DEFERRED
17672 );
17673
17674 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
17675
17676 CREATE TABLE acq.serial_claim_event (
17677     id             BIGSERIAL        PRIMARY KEY,
17678     type           INT              NOT NULL REFERENCES acq.claim_event_type
17679                                              DEFERRABLE INITIALLY DEFERRED,
17680     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
17681                                              DEFERRABLE INITIALLY DEFERRED,
17682     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
17683     creator        INT              NOT NULL REFERENCES actor.usr
17684                                              DEFERRABLE INITIALLY DEFERRED,
17685     note           TEXT
17686 );
17687
17688 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
17689
17690 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
17691
17692 -- now what about the auditor.*_lifecycle views??
17693
17694 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17695     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
17696 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17697     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
17698 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
17699 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
17700
17701 CREATE TABLE asset.call_number_class (
17702     id             bigserial     PRIMARY KEY,
17703     name           TEXT          NOT NULL,
17704     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
17705     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17706 );
17707
17708 COMMENT ON TABLE asset.call_number_class IS $$
17709 Defines the call number normalization database functions in the "normalizer"
17710 column and the tag/subfield combinations to use to lookup the call number in
17711 the "field" column for a given classification scheme. Tag/subfield combinations
17712 are delimited by commas.
17713 $$;
17714
17715 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
17716     ('Generic', 'asset.label_normalizer_generic'),
17717     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
17718     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
17719 ;
17720
17721 -- Generic fields
17722 UPDATE asset.call_number_class
17723     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17724     WHERE id = 1
17725 ;
17726
17727 -- Dewey fields
17728 UPDATE asset.call_number_class
17729     SET field = '080ab,082ab'
17730     WHERE id = 2
17731 ;
17732
17733 -- LC fields
17734 UPDATE asset.call_number_class
17735     SET field = '050ab,055ab'
17736     WHERE id = 3
17737 ;
17738  
17739 ALTER TABLE asset.call_number
17740         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
17741                 REFERENCES asset.call_number_class(id)
17742                 DEFERRABLE INITIALLY DEFERRED;
17743
17744 ALTER TABLE asset.call_number
17745         ADD COLUMN label_sortkey TEXT;
17746
17747 CREATE INDEX asset_call_number_label_sortkey
17748         ON asset.call_number(label_sortkey);
17749
17750 ALTER TABLE auditor.asset_call_number_history
17751         ADD COLUMN label_class BIGINT;
17752
17753 ALTER TABLE auditor.asset_call_number_history
17754         ADD COLUMN label_sortkey TEXT;
17755
17756 -- Pick up the new columns in dependent views
17757
17758 DROP VIEW auditor.asset_call_number_lifecycle;
17759
17760 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17761
17762 DROP VIEW auditor.asset_call_number_lifecycle;
17763
17764 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17765
17766 DROP VIEW IF EXISTS stats.fleshed_call_number;
17767
17768 CREATE VIEW stats.fleshed_call_number AS
17769         SELECT  cn.*,
17770             CAST(cn.create_date AS DATE) AS create_date_day,
17771         CAST(cn.edit_date AS DATE) AS edit_date_day,
17772         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
17773         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
17774             rd.item_lang,
17775                 rd.item_type,
17776                 rd.item_form
17777         FROM    asset.call_number cn
17778                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
17779
17780 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
17781 DECLARE
17782     sortkey        TEXT := '';
17783 BEGIN
17784     sortkey := NEW.label_sortkey;
17785
17786     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
17787        quote_literal( NEW.label ) || ')'
17788        FROM asset.call_number_class acnc
17789        WHERE acnc.id = NEW.label_class
17790        INTO sortkey;
17791
17792     NEW.label_sortkey = sortkey;
17793
17794     RETURN NEW;
17795 END;
17796 $func$ LANGUAGE PLPGSQL;
17797
17798 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
17799     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
17800     # thus could probably be considered a derived work, although nothing was
17801     # directly copied - but to err on the safe side of providing attribution:
17802     # Copyright (C) 2007 LibLime
17803     # Licensed under the GPL v2 or later
17804
17805     use strict;
17806     use warnings;
17807
17808     # Converts the callnumber to uppercase
17809     # Strips spaces from start and end of the call number
17810     # Converts anything other than letters, digits, and periods into underscores
17811     # Collapses multiple underscores into a single underscore
17812     my $callnum = uc(shift);
17813     $callnum =~ s/^\s//g;
17814     $callnum =~ s/\s$//g;
17815     $callnum =~ s/[^A-Z0-9_.]/_/g;
17816     $callnum =~ s/_{2,}/_/g;
17817
17818     return $callnum;
17819 $func$ LANGUAGE PLPERLU;
17820
17821 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
17822     # Derived from the Koha C4::ClassSortRoutine::Dewey module
17823     # Copyright (C) 2007 LibLime
17824     # Licensed under the GPL v2 or later
17825
17826     use strict;
17827     use warnings;
17828
17829     my $init = uc(shift);
17830     $init =~ s/^\s+//;
17831     $init =~ s/\s+$//;
17832     $init =~ s!/!!g;
17833     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
17834     my @tokens = split /\.|\s+/, $init;
17835     my $digit_group_count = 0;
17836     for (my $i = 0; $i <= $#tokens; $i++) {
17837         if ($tokens[$i] =~ /^\d+$/) {
17838             $digit_group_count++;
17839             if (2 == $digit_group_count) {
17840                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
17841                 $tokens[$i] =~ tr/ /0/;
17842             }
17843         }
17844     }
17845     my $key = join("_", @tokens);
17846     $key =~ s/[^\p{IsAlnum}_]//g;
17847
17848     return $key;
17849
17850 $func$ LANGUAGE PLPERLU;
17851
17852 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
17853     use strict;
17854     use warnings;
17855
17856     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
17857     # The author hopes to upload it to CPAN some day, which would make our lives easier
17858     use Library::CallNumber::LC;
17859
17860     my $callnum = Library::CallNumber::LC->new(shift);
17861     return $callnum->normalize();
17862
17863 $func$ LANGUAGE PLPERLU;
17864
17865 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$
17866 DECLARE
17867     ans RECORD;
17868     trans INT;
17869 BEGIN
17870     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;
17871
17872     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
17873         RETURN QUERY
17874         SELECT  ans.depth,
17875                 ans.id,
17876                 COUNT( av.id ),
17877                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
17878                 COUNT( av.id ),
17879                 trans
17880           FROM
17881                 actor.org_unit_descendants(ans.id) d
17882                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
17883                 JOIN asset.copy cp ON (cp.id = av.id)
17884           GROUP BY 1,2,6;
17885
17886         IF NOT FOUND THEN
17887             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
17888         END IF;
17889
17890     END LOOP;
17891
17892     RETURN;
17893 END;
17894 $f$ LANGUAGE PLPGSQL;
17895
17896 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$
17897 DECLARE
17898     ans RECORD;
17899     trans INT;
17900 BEGIN
17901     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;
17902
17903     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
17904         RETURN QUERY
17905         SELECT  -1,
17906                 ans.id,
17907                 COUNT( av.id ),
17908                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
17909                 COUNT( av.id ),
17910                 trans
17911           FROM
17912                 actor.org_unit_descendants(ans.id) d
17913                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
17914                 JOIN asset.copy cp ON (cp.id = av.id)
17915           GROUP BY 1,2,6;
17916
17917         IF NOT FOUND THEN
17918             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
17919         END IF;
17920
17921     END LOOP;
17922
17923     RETURN;
17924 END;
17925 $f$ LANGUAGE PLPGSQL;
17926
17927 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$
17928 DECLARE
17929     ans RECORD;
17930     trans INT;
17931 BEGIN
17932     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;
17933
17934     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
17935         RETURN QUERY
17936         SELECT  ans.depth,
17937                 ans.id,
17938                 COUNT( cp.id ),
17939                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
17940                 COUNT( cp.id ),
17941                 trans
17942           FROM
17943                 actor.org_unit_descendants(ans.id) d
17944                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
17945                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
17946           GROUP BY 1,2,6;
17947
17948         IF NOT FOUND THEN
17949             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
17950         END IF;
17951
17952     END LOOP;
17953
17954     RETURN;
17955 END;
17956 $f$ LANGUAGE PLPGSQL;
17957
17958 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$
17959 DECLARE
17960     ans RECORD;
17961     trans INT;
17962 BEGIN
17963     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;
17964
17965     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
17966         RETURN QUERY
17967         SELECT  -1,
17968                 ans.id,
17969                 COUNT( cp.id ),
17970                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
17971                 COUNT( cp.id ),
17972                 trans
17973           FROM
17974                 actor.org_unit_descendants(ans.id) d
17975                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
17976                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
17977           GROUP BY 1,2,6;
17978
17979         IF NOT FOUND THEN
17980             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
17981         END IF;
17982
17983     END LOOP;
17984
17985     RETURN;
17986 END;
17987 $f$ LANGUAGE PLPGSQL;
17988
17989 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$
17990 BEGIN
17991     IF staff IS TRUE THEN
17992         IF place > 0 THEN
17993             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
17994         ELSE
17995             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
17996         END IF;
17997     ELSE
17998         IF place > 0 THEN
17999             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18000         ELSE
18001             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18002         END IF;
18003     END IF;
18004
18005     RETURN;
18006 END;
18007 $f$ LANGUAGE PLPGSQL;
18008
18009 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$
18010 DECLARE
18011     ans RECORD;
18012     trans INT;
18013 BEGIN
18014     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;
18015
18016     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
18017         RETURN QUERY
18018         SELECT  ans.depth,
18019                 ans.id,
18020                 COUNT( av.id ),
18021                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18022                 COUNT( av.id ),
18023                 trans
18024           FROM
18025                 actor.org_unit_descendants(ans.id) d
18026                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18027                 JOIN asset.copy cp ON (cp.id = av.id)
18028                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18029           GROUP BY 1,2,6;
18030
18031         IF NOT FOUND THEN
18032             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18033         END IF;
18034
18035     END LOOP;
18036
18037     RETURN;
18038 END;
18039 $f$ LANGUAGE PLPGSQL;
18040
18041 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$
18042 DECLARE
18043     ans RECORD;
18044     trans INT;
18045 BEGIN
18046     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;
18047
18048     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18049         RETURN QUERY
18050         SELECT  -1,
18051                 ans.id,
18052                 COUNT( av.id ),
18053                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18054                 COUNT( av.id ),
18055                 trans
18056           FROM
18057                 actor.org_unit_descendants(ans.id) d
18058                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18059                 JOIN asset.copy cp ON (cp.id = av.id)
18060                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18061           GROUP BY 1,2,6;
18062
18063         IF NOT FOUND THEN
18064             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18065         END IF;
18066
18067     END LOOP;
18068
18069     RETURN;
18070 END;
18071 $f$ LANGUAGE PLPGSQL;
18072
18073 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$
18074 DECLARE
18075     ans RECORD;
18076     trans INT;
18077 BEGIN
18078     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;
18079
18080     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
18081         RETURN QUERY
18082         SELECT  ans.depth,
18083                 ans.id,
18084                 COUNT( cp.id ),
18085                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18086                 COUNT( cp.id ),
18087                 trans
18088           FROM
18089                 actor.org_unit_descendants(ans.id) d
18090                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18091                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18092                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18093           GROUP BY 1,2,6;
18094
18095         IF NOT FOUND THEN
18096             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18097         END IF;
18098
18099     END LOOP;
18100
18101     RETURN;
18102 END;
18103 $f$ LANGUAGE PLPGSQL;
18104
18105 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$
18106 DECLARE
18107     ans RECORD;
18108     trans INT;
18109 BEGIN
18110     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;
18111
18112     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18113         RETURN QUERY
18114         SELECT  -1,
18115                 ans.id,
18116                 COUNT( cp.id ),
18117                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18118                 COUNT( cp.id ),
18119                 trans
18120           FROM
18121                 actor.org_unit_descendants(ans.id) d
18122                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18123                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18124                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18125           GROUP BY 1,2,6;
18126
18127         IF NOT FOUND THEN
18128             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18129         END IF;
18130
18131     END LOOP;
18132
18133     RETURN;
18134 END;
18135 $f$ LANGUAGE PLPGSQL;
18136
18137 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$
18138 BEGIN
18139     IF staff IS TRUE THEN
18140         IF place > 0 THEN
18141             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18142         ELSE
18143             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18144         END IF;
18145     ELSE
18146         IF place > 0 THEN
18147             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18148         ELSE
18149             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18150         END IF;
18151     END IF;
18152
18153     RETURN;
18154 END;
18155 $f$ LANGUAGE PLPGSQL;
18156
18157 -- No transaction is required
18158
18159 -- Triggers on the vandelay.queued_*_record tables delete entries from
18160 -- the associated vandelay.queued_*_record_attr tables based on the record's
18161 -- ID; create an index on that column to avoid sequential scans for each
18162 -- queued record that is deleted
18163 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18164 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18165
18166 -- Avoid sequential scans for queue retrieval operations by providing an
18167 -- index on the queue column
18168 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18169 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18170
18171 -- Start picking up call number label prefixes and suffixes
18172 -- from asset.copy_location
18173 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18174 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18175
18176 DROP VIEW auditor.asset_copy_lifecycle;
18177
18178 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18179
18180 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18181
18182 -- Let's not break existing reports
18183 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18184 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18185
18186 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18187 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18188 SELECT  r.id,
18189     r.fingerprint,
18190     r.quality,
18191     r.tcn_source,
18192     r.tcn_value,
18193     FIRST(title.value) AS title,
18194     FIRST(author.value) AS author,
18195     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18196     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18197     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18198     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18199   FROM  biblio.record_entry r
18200     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18201     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18202     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18203     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18204     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18205     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18206   GROUP BY 1,2,3,4,5;
18207
18208 -- Correct the ISSN array definition for reporter.simple_record
18209
18210 CREATE OR REPLACE VIEW reporter.simple_record AS
18211 SELECT  r.id,
18212         s.metarecord,
18213         r.fingerprint,
18214         r.quality,
18215         r.tcn_source,
18216         r.tcn_value,
18217         title.value AS title,
18218         uniform_title.value AS uniform_title,
18219         author.value AS author,
18220         publisher.value AS publisher,
18221         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18222         series_title.value AS series_title,
18223         series_statement.value AS series_statement,
18224         summary.value AS summary,
18225         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18226         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18227         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18228         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18229         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18230         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18231         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18232         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
18233   FROM  biblio.record_entry r
18234         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18235         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18236         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18237         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18238         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18239         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18240         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18241         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18242         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')
18243         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18244         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18245   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18246
18247 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18248     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
18249 $$ LANGUAGE SQL;
18250
18251 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18252 BEGIN
18253     IF TG_OP = 'DELETE' THEN
18254         PERFORM reporter.simple_rec_delete(NEW.id);
18255     ELSE
18256         PERFORM reporter.simple_rec_update(NEW.id);
18257     END IF;
18258
18259     RETURN NEW;
18260 END;
18261 $func$ LANGUAGE PLPGSQL;
18262
18263 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
18264
18265 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18266
18267 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18268
18269 UPDATE config.org_unit_setting_type
18270     SET view_perm = (SELECT id FROM permission.perm_list
18271         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18272     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18273
18274 UPDATE config.org_unit_setting_type
18275     SET update_perm = (SELECT id FROM permission.perm_list
18276         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18277     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18278
18279 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18280     VALUES (
18281         'opac.fully_compressed_serial_holdings',
18282         'OPAC: Use fully compressed serial holdings',
18283         'Show fully compressed serial holdings for all libraries at and below
18284         the current context unit',
18285         'bool'
18286     );
18287
18288 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18289     use strict;
18290     use warnings;
18291
18292     use utf8;
18293     use MARC::Record;
18294     use MARC::File::XML (BinaryEncoding => 'UTF8');
18295     use UUID::Tiny ':std';
18296
18297     my $xml = shift() or return undef;
18298
18299     my $r;
18300
18301     # Prevent errors in XML parsing from blowing out ungracefully
18302     eval {
18303         $r = MARC::Record->new_from_xml( $xml );
18304         1;
18305     } or do {
18306        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18307     };
18308
18309     if (!$r) {
18310        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18311     }
18312
18313     # From http://www.loc.gov/standards/sourcelist/subject.html
18314     my $thes_code_map = {
18315         a => 'lcsh',
18316         b => 'lcshac',
18317         c => 'mesh',
18318         d => 'nal',
18319         k => 'cash',
18320         n => 'notapplicable',
18321         r => 'aat',
18322         s => 'sears',
18323         v => 'rvm',
18324     };
18325
18326     # Default to "No attempt to code" if the leader is horribly broken
18327     my $fixed_field = $r->field('008');
18328     my $thes_char = '|';
18329     if ($fixed_field) {
18330         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18331     }
18332
18333     my $thes_code = 'UNDEFINED';
18334
18335     if ($thes_char eq 'z') {
18336         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18337         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18338     } elsif ($thes_code_map->{$thes_char}) {
18339         $thes_code = $thes_code_map->{$thes_char};
18340     }
18341
18342     my $auth_txt = '';
18343     my $head = $r->field('1..');
18344     if ($head) {
18345         # Concatenate all of these subfields together, prefixed by their code
18346         # to prevent collisions along the lines of "Fiction, North Carolina"
18347         foreach my $sf ($head->subfields()) {
18348             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18349         }
18350     }
18351
18352     # Perhaps better to parameterize the spi and pass as a parameter
18353     $auth_txt =~ s/'//go;
18354
18355     if ($auth_txt) {
18356         my $result = spi_exec_query("SELECT public.naco_normalize('$auth_txt') AS norm_text");
18357         my $norm_txt = $result->{rows}[0]->{norm_text};
18358         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18359     }
18360
18361     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18362 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18363
18364 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18365 /**
18366 * Extract the authority heading, thesaurus, and NACO-normalized values
18367 * from an authority record. The primary purpose is to build a unique
18368 * index to defend against duplicated authority records from the same
18369 * thesaurus.
18370 */
18371 $$;
18372
18373 DROP INDEX authority.authority_record_unique_tcn;
18374 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18375 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18376
18377 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
18378
18379 CREATE INDEX by_heading_and_thesaurus
18380     ON authority.record_entry (authority.normalize_heading(marc))
18381     WHERE deleted IS FALSE or deleted = FALSE
18382 ;
18383
18384 ALTER TABLE acq.provider_contact
18385         ALTER COLUMN name SET NOT NULL;
18386
18387 ALTER TABLE actor.stat_cat
18388         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18389
18390 -- Recreate some foreign keys that were somehow dropped, probably
18391 -- by some kind of cascade from an inherited table:
18392
18393 ALTER TABLE action.reservation_transit_copy
18394         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
18395                 REFERENCES booking.resource(id)
18396                 ON DELETE CASCADE
18397                 DEFERRABLE INITIALLY DEFERRED,
18398         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
18399                 REFERENCES booking.reservation(id)
18400                 ON DELETE SET NULL
18401                 DEFERRABLE INITIALLY DEFERRED;
18402
18403 CREATE INDEX user_bucket_item_target_user_idx
18404         ON container.user_bucket_item ( target_user );
18405
18406 CREATE INDEX m_c_t_collector_idx
18407         ON money.collections_tracker ( collector );
18408
18409 CREATE INDEX aud_actor_usr_address_hist_id_idx
18410         ON auditor.actor_usr_address_history ( id );
18411
18412 CREATE INDEX aud_actor_usr_hist_id_idx
18413         ON auditor.actor_usr_history ( id );
18414
18415 CREATE INDEX aud_asset_cn_hist_creator_idx
18416         ON auditor.asset_call_number_history ( creator );
18417
18418 CREATE INDEX aud_asset_cn_hist_editor_idx
18419         ON auditor.asset_call_number_history ( editor );
18420
18421 CREATE INDEX aud_asset_cp_hist_creator_idx
18422         ON auditor.asset_copy_history ( creator );
18423
18424 CREATE INDEX aud_asset_cp_hist_editor_idx
18425         ON auditor.asset_copy_history ( editor );
18426
18427 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18428         ON auditor.biblio_record_entry_history ( creator );
18429
18430 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18431         ON auditor.biblio_record_entry_history ( editor );
18432
18433 CREATE TABLE action.hold_request_note (
18434
18435     id     BIGSERIAL PRIMARY KEY,
18436     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18437                               ON DELETE CASCADE
18438                               DEFERRABLE INITIALLY DEFERRED,
18439     title  TEXT      NOT NULL,
18440     body   TEXT      NOT NULL,
18441     slip   BOOL      NOT NULL DEFAULT FALSE,
18442     pub    BOOL      NOT NULL DEFAULT FALSE,
18443     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18444
18445 );
18446 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18447
18448 -- Tweak a constraint to add a CASCADE
18449
18450 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18451
18452 ALTER TABLE action.hold_notification
18453         ADD CONSTRAINT hold_notification_hold_fkey
18454                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18455                 ON DELETE CASCADE
18456                 DEFERRABLE INITIALLY DEFERRED;
18457
18458 CREATE TRIGGER asset_label_sortkey_trigger
18459     BEFORE UPDATE OR INSERT ON asset.call_number
18460     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18461
18462 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18463 RETURNS VOID AS $$
18464 --
18465 -- Delete expired circulation bucket items for all users that have
18466 -- a setting for patron.max_reading_list_interval.
18467 --
18468 DECLARE
18469     today        TIMESTAMP WITH TIME ZONE;
18470     threshold    TIMESTAMP WITH TIME ZONE;
18471         usr_setting  RECORD;
18472 BEGIN
18473         SELECT date_trunc( 'day', now() ) INTO today;
18474         --
18475         FOR usr_setting in
18476                 SELECT
18477                         usr,
18478                         value
18479                 FROM
18480                         actor.usr_setting
18481                 WHERE
18482                         name = 'patron.max_reading_list_interval'
18483         LOOP
18484                 --
18485                 -- Make sure the setting is a valid interval
18486                 --
18487                 BEGIN
18488                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18489                 EXCEPTION
18490                         WHEN OTHERS THEN
18491                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18492                                         usr_setting.usr, usr_setting.value;
18493                                 CONTINUE;
18494                 END;
18495                 --
18496                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18497                 --
18498         DELETE FROM container.copy_bucket_item
18499         WHERE
18500                 bucket IN
18501                 (
18502                     SELECT
18503                         id
18504                     FROM
18505                         container.copy_bucket
18506                     WHERE
18507                         owner = usr_setting.usr
18508                         AND btype = 'circ_history'
18509                 )
18510                 AND create_time < threshold;
18511         END LOOP;
18512         --
18513 END;
18514 $$ LANGUAGE plpgsql;
18515
18516 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18517 /*
18518  * Delete expired circulation bucket items for all users that have
18519  * a setting for patron.max_reading_list_interval.
18520 */
18521 $$;
18522
18523 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18524          ac_usr IN INTEGER
18525 ) RETURNS VOID AS $$
18526 --
18527 -- Delete old circulation bucket items for a specified user.
18528 -- "Old" means older than the interval specified by a
18529 -- user-level setting, if it is so specified.
18530 --
18531 DECLARE
18532     threshold TIMESTAMP WITH TIME ZONE;
18533 BEGIN
18534         -- Sanity check
18535         IF ac_usr IS NULL THEN
18536                 RETURN;
18537         END IF;
18538         -- Determine the threshold date that defines "old".  Subtract the
18539         -- interval from the system date, then truncate to midnight.
18540         SELECT
18541                 date_trunc( 
18542                         'day',
18543                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18544                 )
18545         INTO
18546                 threshold
18547         FROM
18548                 actor.usr_setting
18549         WHERE
18550                 usr = ac_usr
18551                 AND name = 'patron.max_reading_list_interval';
18552         --
18553         IF threshold is null THEN
18554                 -- No interval defined; don't delete anything
18555                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18556                 return;
18557         END IF;
18558         --
18559         -- RAISE NOTICE 'Date threshold: %', threshold;
18560         --
18561         -- Threshold found; do the delete
18562         delete from container.copy_bucket_item
18563         where
18564                 bucket in
18565                 (
18566                         select
18567                                 id
18568                         from
18569                                 container.copy_bucket
18570                         where
18571                                 owner = ac_usr
18572                                 and btype = 'circ_history'
18573                 )
18574                 and create_time < threshold;
18575         --
18576         RETURN;
18577 END;
18578 $$ LANGUAGE plpgsql;
18579
18580 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18581 /*
18582  * Delete old circulation bucket items for a specified user.
18583  * "Old" means older than the interval specified by a
18584  * user-level setting, if it is so specified.
18585 */
18586 $$;
18587
18588 COMMIT;
18589
18590 -- Some operations go outside of the transaction, because they may
18591 -- legitimately fail.
18592
18593 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
18594 \qecho doesn't exist; ignore those errors if they occur.
18595
18596 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
18597
18598 ALTER TABLE auditor.action_hold_request_history
18599 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
18600
18601 ALTER TABLE auditor.action_hold_request_history
18602 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
18603
18604 \qecho Outside of the transaction: adding indexes that may or may not exist.
18605 \qecho If any of these CREATE INDEX statements fails because the index already
18606 \qecho exists, ignore the failure.
18607
18608 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
18609 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
18610 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
18611 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
18612 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
18613 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
18614 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
18615 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
18616 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
18617 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
18618 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
18619 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
18620 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
18621 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
18622 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
18623 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
18624 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
18625 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
18626 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
18627 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
18628 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
18629 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
18630 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
18631 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
18632 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
18633 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
18634 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
18635 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
18636 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
18637 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
18638 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
18639 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
18640 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
18641
18642 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
18643
18644 \qecho if the following CREATE INDEX fails, It will be necessary to do some
18645 \qecho data cleanup as described in the comments.
18646
18647 -- If the unique index fails, uncomment the following to create
18648 -- a regular index that will help find the duplicates in a hurry:
18649 --CREATE INDEX by_heading_and_thesaurus
18650 --    ON authority.record_entry (authority.normalize_heading(marc))
18651 --    WHERE deleted IS FALSE or deleted = FALSE
18652 --;
18653
18654 -- Then find the duplicates like so to get an idea of how much
18655 -- pain you're looking at to clean things up:
18656 --SELECT id, authority.normalize_heading(marc)
18657 --    FROM authority.record_entry
18658 --    WHERE authority.normalize_heading(marc) IN (
18659 --        SELECT authority.normalize_heading(marc)
18660 --        FROM authority.record_entry
18661 --        GROUP BY authority.normalize_heading(marc)
18662 --        HAVING COUNT(*) > 1
18663 --    )
18664 --;
18665
18666 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
18667 -- statement succeeds, drop the temporary index to avoid unnecessary
18668 -- duplication:
18669 -- DROP INDEX authority.by_heading_and_thesaurus;
18670
18671 \qecho Upgrade script completed.