]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
need to update this index as well
[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 DROP INDEX asset.asset_call_number_upper_label_id_owning_lib_idx;
5 CREATE INDEX asset_call_number_upper_label_id_owning_lib_idx ON asset.call_number (oils_text_as_bytea(upper(label)),id,owning_lib);
6
7
8 \qecho Before starting the transaction: drop some constraints.
9 \qecho If a DROP fails because the constraint doesn't exist, ignore the failure.
10
11 ALTER TABLE permission.grp_perm_map        DROP CONSTRAINT grp_perm_map_perm_fkey;
12 ALTER TABLE permission.usr_perm_map        DROP CONSTRAINT usr_perm_map_perm_fkey;
13 ALTER TABLE permission.usr_object_perm_map DROP CONSTRAINT usr_object_perm_map_perm_fkey;
14 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_or_record_once_per_owner;
15 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_once_per_owner;
16
17 \qecho Beginning the transaction now
18
19 BEGIN;
20
21 -- Highest-numbered individual upgrade script incorporated herein:
22
23 INSERT INTO config.upgrade_log (version) VALUES ('0433');
24
25 -- Recreate one of the constraints that we just dropped,
26 -- under a different name:
27
28 ALTER TABLE booking.resource_type
29         ADD CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record);
30
31 -- Now upgrade permission.perm_list.  This is fairly complicated.
32
33 -- Add ON UPDATE CASCADE to some foreign keys so that, when we renumber the
34 -- permissions, the dependents will follow and stay in sync:
35
36 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
37     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
38
39 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_perm_map_perm_fkey FOREIGN KEY (perm)
40     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
41
42 ALTER TABLE permission.usr_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
43     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
44
45 UPDATE permission.perm_list
46     SET code = 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow'
47     WHERE code = 'UPDATE_ORG_UNIT_SETTING.global.credit.allow';
48
49 -- The following UPDATES were originally in an individual upgrade script, but should
50 -- no longer be necessary now that the foreign key has an ON UPDATE CASCADE clause.
51 -- We retain the UPDATES here, commented out, as historical relics.
52
53 -- UPDATE permission.grp_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
54 -- UPDATE permission.usr_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
55
56 -- Spelling correction
57 UPDATE permission.perm_list SET code = 'ADMIN_RECURRING_FINE_RULE' WHERE code = 'ADMIN_RECURING_FINE_RULE';
58
59 -- Now we engage in a Great Renumbering of the permissions in permission.perm_list,
60 -- in order to clean up accumulated cruft.
61
62 -- The first step is to establish some triggers so that, when we change the id of a permission,
63 -- the associated translations are updated accordingly.
64
65 CREATE OR REPLACE FUNCTION oils_i18n_update_apply(old_ident TEXT, new_ident TEXT, hint TEXT) RETURNS VOID AS $_$
66 BEGIN
67
68     EXECUTE $$
69         UPDATE  config.i18n_core
70           SET   identity_value = $$ || quote_literal( new_ident ) || $$ 
71           WHERE fq_field LIKE '$$ || hint || $$.%' 
72                 AND identity_value = $$ || quote_literal( old_ident ) || $$;$$;
73
74     RETURN;
75
76 END;
77 $_$ LANGUAGE PLPGSQL;
78
79 CREATE OR REPLACE FUNCTION oils_i18n_id_tracking(/* hint */) RETURNS TRIGGER AS $_$
80 BEGIN
81     PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
82     RETURN NEW;
83 END;
84 $_$ LANGUAGE PLPGSQL;
85
86 CREATE OR REPLACE FUNCTION oils_i18n_code_tracking(/* hint */) RETURNS TRIGGER AS $_$
87 BEGIN
88     PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
89     RETURN NEW;
90 END;
91 $_$ LANGUAGE PLPGSQL;
92
93
94 CREATE TRIGGER maintain_perm_i18n_tgr
95     AFTER UPDATE ON permission.perm_list
96     FOR EACH ROW EXECUTE PROCEDURE oils_i18n_id_tracking('ppl');
97
98 -- Next, create a new table as a convenience for sloshing data back and forth,
99 -- and for recording which permission went where.  It looks just like
100 -- permission.perm_list, but with two extra columns: one for the old id, and one to
101 -- distinguish between predefined permissions and non-predefined permissions.
102
103 -- This table is, in effect, a temporary table, because we can drop it once the
104 -- upgrade is complete.  It is not technically temporary as far as PostgreSQL is
105 -- concerned, because we don't want it to disappear at the end of the session.
106 -- We keep it around so that we have a map showing the old id and the new id for
107 -- each permission.  However there is no IDL entry for it, nor is it defined
108 -- in the base sql files.
109
110 CREATE TABLE permission.temp_perm (
111         id          INT        PRIMARY KEY,
112         code        TEXT       UNIQUE,
113         description TEXT,
114         old_id      INT,
115         predefined  BOOL       NOT NULL DEFAULT TRUE
116 );
117
118 -- Populate the temp table with a definitive set of predefined permissions,
119 -- hard-coding the ids.
120
121 -- The first set of permissions is derived from the database, as loaded in a
122 -- loaded 1.6.1 database, plus a few changes previously applied in this upgrade
123 -- script.  The second set is derived from the IDL -- permissions that are referenced
124 -- in <permacrud> elements but not defined in the database.
125
126 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( -1, 'EVERYTHING',
127      '' );
128 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 1, 'OPAC_LOGIN',
129      'Allow a user to log in to the OPAC' );
130 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 2, 'STAFF_LOGIN',
131      'Allow a user to log in to the staff client' );
132 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 3, 'MR_HOLDS',
133      'Allow a user to create a metarecord holds' );
134 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 4, 'TITLE_HOLDS',
135      'Allow a user to place a hold at the title level' );
136 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 5, 'VOLUME_HOLDS',
137      'Allow a user to place a volume level hold' );
138 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 6, 'COPY_HOLDS',
139      'Allow a user to place a hold on a specific copy' );
140 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 7, 'REQUEST_HOLDS',
141      '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)' );
142 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 8, 'REQUEST_HOLDS_OVERRIDE',
143      '* no longer applicable' );
144 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 9, 'VIEW_HOLD',
145      'Allow a user to view another user''s holds' );
146 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 10, 'DELETE_HOLDS',
147      '* no longer applicable' );
148 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 11, 'UPDATE_HOLD',
149      'Allow a user to update another user''s hold' );
150 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 12, 'RENEW_CIRC',
151      'Allow a user to renew items' );
152 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 13, 'VIEW_USER_FINES_SUMMARY',
153      'Allow a user to view bill details' );
154 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 14, 'VIEW_USER_TRANSACTIONS',
155      'Allow a user to see another user''s grocery or circulation transactions in the Bills Interface; duplicate of VIEW_TRANSACTION' );
156 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 15, 'UPDATE_MARC',
157      'Allow a user to edit a MARC record' );
158 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 16, 'CREATE_MARC',
159      'Allow a user to create new MARC records' );
160 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 17, 'IMPORT_MARC',
161      'Allow a user to import a MARC record via the Z39.50 interface' );
162 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 18, 'CREATE_VOLUME',
163      'Allow a user to create a volume' );
164 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 19, 'UPDATE_VOLUME',
165      '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.' );
166 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 20, 'DELETE_VOLUME',
167      'Allow a user to delete a volume' );
168 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 21, 'CREATE_COPY',
169      'Allow a user to create a new copy object' );
170 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 22, 'UPDATE_COPY',
171      'Allow a user to edit a copy' );
172 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 23, 'DELETE_COPY',
173      'Allow a user to delete a copy' );
174 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 24, 'RENEW_HOLD_OVERRIDE',
175      'Allow a user to continue to renew an item even if it is required for a hold' );
176 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 25, 'CREATE_USER',
177      'Allow a user to create another user' );
178 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 26, 'UPDATE_USER',
179      'Allow a user to edit a user''s record' );
180 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 27, 'DELETE_USER',
181      'Allow a user to mark a user as deleted' );
182 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 28, 'VIEW_USER',
183      'Allow a user to view another user''s Patron Record' );
184 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 29, 'COPY_CHECKIN',
185      'Allow a user to check in a copy' );
186 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 30, 'CREATE_TRANSIT',
187      'Allow a user to place an item in transit' );
188 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 31, 'VIEW_PERMISSION',
189      'Allow a user to view user permissions within the user permissions editor' );
190 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 32, 'CHECKIN_BYPASS_HOLD_FULFILL',
191      '* no longer applicable' );
192 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 33, 'CREATE_PAYMENT',
193      'Allow a user to record payments in the Billing Interface' );
194 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 34, 'SET_CIRC_LOST',
195      'Allow a user to mark an item as ''lost''' );
196 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 35, 'SET_CIRC_MISSING',
197      'Allow a user to mark an item as ''missing''' );
198 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 36, 'SET_CIRC_CLAIMS_RETURNED',
199      'Allow a user to mark an item as ''claims returned''' );
200 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 37, 'CREATE_TRANSACTION',
201      'Allow a user to create a new billable transaction' );
202 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 38, 'VIEW_TRANSACTION',
203      'Allow a user may view another user''s transactions' );
204 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 39, 'CREATE_BILL',
205      'Allow a user to create a new bill on a transaction' );
206 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 40, 'VIEW_CONTAINER',
207      'Allow a user to view another user''s containers (buckets)' );
208 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 41, 'CREATE_CONTAINER',
209      'Allow a user to create a new container for another user' );
210 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 42, 'UPDATE_ORG_UNIT',
211      'Allow a user to change the settings for an organization unit' );
212 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 43, 'VIEW_CIRCULATIONS',
213      'Allow a user to see what another user has checked out' );
214 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 44, 'DELETE_CONTAINER',
215      'Allow a user to delete another user''s container' );
216 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 45, 'CREATE_CONTAINER_ITEM',
217      'Allow a user to create a container item for another user' );
218 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 46, 'CREATE_USER_GROUP_LINK',
219      'Allow a user to add other users to permission groups' );
220 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 47, 'REMOVE_USER_GROUP_LINK',
221      'Allow a user to remove other users from permission groups' );
222 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 48, 'VIEW_PERM_GROUPS',
223      'Allow a user to view other users'' permission groups' );
224 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 49, 'VIEW_PERMIT_CHECKOUT',
225      'Allow a user to determine whether another user can check out an item' );
226 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 50, 'UPDATE_BATCH_COPY',
227      'Allow a user to edit copies in batch' );
228 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 51, 'CREATE_PATRON_STAT_CAT',
229      'User may create a new patron statistical category' );
230 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 52, 'CREATE_COPY_STAT_CAT',
231      'User may create a copy statistical category' );
232 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 53, 'CREATE_PATRON_STAT_CAT_ENTRY',
233      'User may create an entry in a patron statistical category' );
234 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 54, 'CREATE_COPY_STAT_CAT_ENTRY',
235      'User may create an entry in a copy statistical category' );
236 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 55, 'UPDATE_PATRON_STAT_CAT',
237      'User may update a patron statistical category' );
238 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 56, 'UPDATE_COPY_STAT_CAT',
239      'User may update a copy statistical category' );
240 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 57, 'UPDATE_PATRON_STAT_CAT_ENTRY',
241      'User may update an entry in a patron statistical category' );
242 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 58, 'UPDATE_COPY_STAT_CAT_ENTRY',
243      'User may update an entry in a copy statistical category' );
244 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 59, 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
245      'User may link another user to an entry in a statistical category' );
246 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 60, 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
247      'User may link a copy to an entry in a statistical category' );
248 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 61, 'DELETE_PATRON_STAT_CAT',
249      'User may delete a patron statistical category' );
250 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 62, 'DELETE_COPY_STAT_CAT',
251      'User may delete a copy statistical category' );
252 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 63, 'DELETE_PATRON_STAT_CAT_ENTRY',
253      'User may delete an entry from a patron statistical category' );
254 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 64, 'DELETE_COPY_STAT_CAT_ENTRY',
255      'User may delete an entry from a copy statistical category' );
256 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 65, 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
257      'User may delete a patron statistical category entry map' );
258 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 66, 'DELETE_COPY_STAT_CAT_ENTRY_MAP',
259      'User may delete a copy statistical category entry map' );
260 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 67, 'CREATE_NON_CAT_TYPE',
261      'Allow a user to create a new non-cataloged item type' );
262 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 68, 'UPDATE_NON_CAT_TYPE',
263      'Allow a user to update a non-cataloged item type' );
264 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 69, 'CREATE_IN_HOUSE_USE',
265      'Allow a user to create a new in-house-use ' );
266 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 70, 'COPY_CHECKOUT',
267      'Allow a user to check out a copy' );
268 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 71, 'CREATE_COPY_LOCATION',
269      'Allow a user to create a new copy location' );
270 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 72, 'UPDATE_COPY_LOCATION',
271      'Allow a user to update a copy location' );
272 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 73, 'DELETE_COPY_LOCATION',
273      'Allow a user to delete a copy location' );
274 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 74, 'CREATE_COPY_TRANSIT',
275      'Allow a user to create a transit_copy object for transiting a copy' );
276 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 75, 'COPY_TRANSIT_RECEIVE',
277      'Allow a user to close out a transit on a copy' );
278 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 76, 'VIEW_HOLD_PERMIT',
279      'Allow a user to see if another user has permission to place a hold on a given copy' );
280 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 77, 'VIEW_COPY_CHECKOUT_HISTORY',
281      'Allow a user to view which users have checked out a given copy' );
282 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 78, 'REMOTE_Z3950_QUERY',
283      'Allow a user to perform Z39.50 queries against remote servers' );
284 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 79, 'REGISTER_WORKSTATION',
285      'Allow a user to register a new workstation' );
286 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 80, 'VIEW_COPY_NOTES',
287      'Allow a user to view all notes attached to a copy' );
288 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 81, 'VIEW_VOLUME_NOTES',
289      'Allow a user to view all notes attached to a volume' );
290 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 82, 'VIEW_TITLE_NOTES',
291      'Allow a user to view all notes attached to a title' );
292 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 83, 'CREATE_COPY_NOTE',
293      'Allow a user to create a new copy note' );
294 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 84, 'CREATE_VOLUME_NOTE',
295      'Allow a user to create a new volume note' );
296 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 85, 'CREATE_TITLE_NOTE',
297      'Allow a user to create a new title note' );
298 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 86, 'DELETE_COPY_NOTE',
299      'Allow a user to delete another user''s copy notes' );
300 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 87, 'DELETE_VOLUME_NOTE',
301      'Allow a user to delete another user''s volume note' );
302 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 88, 'DELETE_TITLE_NOTE',
303      'Allow a user to delete another user''s title note' );
304 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 89, 'UPDATE_CONTAINER',
305      'Allow a user to update another user''s container' );
306 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 90, 'CREATE_MY_CONTAINER',
307      'Allow a user to create a container for themselves' );
308 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 91, 'VIEW_HOLD_NOTIFICATION',
309      'Allow a user to view notifications attached to a hold' );
310 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 92, 'CREATE_HOLD_NOTIFICATION',
311      'Allow a user to create new hold notifications' );
312 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 93, 'UPDATE_ORG_SETTING',
313      'Allow a user to update an organization unit setting' );
314 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 94, 'OFFLINE_UPLOAD',
315      'Allow a user to upload an offline script' );
316 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 95, 'OFFLINE_VIEW',
317      'Allow a user to view uploaded offline script information' );
318 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 96, 'OFFLINE_EXECUTE',
319      'Allow a user to execute an offline script batch' );
320 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 97, 'CIRC_OVERRIDE_DUE_DATE',
321      'Allow a user to change the due date on an item to any date' );
322 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 98, 'CIRC_PERMIT_OVERRIDE',
323      'Allow a user to bypass the circulation permit call for check out' );
324 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 99, 'COPY_IS_REFERENCE.override',
325      'Allow a user to override the copy_is_reference event' );
326 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 100, 'VOID_BILLING',
327      'Allow a user to void a bill' );
328 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 101, 'CIRC_CLAIMS_RETURNED.override',
329      'Allow a user to check in or check out an item that has a status of ''claims returned''' );
330 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 102, 'COPY_BAD_STATUS.override',
331      'Allow a user to check out an item in a non-circulatable status' );
332 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 103, 'COPY_ALERT_MESSAGE.override',
333      'Allow a user to check in/out an item that has an alert message' );
334 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 104, 'COPY_STATUS_LOST.override',
335      'Allow a user to remove the lost status from a copy' );
336 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 105, 'COPY_STATUS_MISSING.override',
337      'Allow a user to change the missing status on a copy' );
338 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 106, 'ABORT_TRANSIT',
339      'Allow a user to abort a copy transit if the user is at the transit destination or source' );
340 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 107, 'ABORT_REMOTE_TRANSIT',
341      'Allow a user to abort a copy transit if the user is not at the transit source or dest' );
342 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 108, 'VIEW_ZIP_DATA',
343      'Allow a user to query the ZIP code data method' );
344 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 109, 'CANCEL_HOLDS',
345      'Allow a user to cancel holds' );
346 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 110, 'CREATE_DUPLICATE_HOLDS',
347      'Allow a user to create duplicate holds (two or more holds on the same title)' );
348 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 111, 'actor.org_unit.closed_date.delete',
349      'Allow a user to remove a closed date interval for a given location' );
350 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 112, 'actor.org_unit.closed_date.update',
351      'Allow a user to update a closed date interval for a given location' );
352 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 113, 'actor.org_unit.closed_date.create',
353      'Allow a user to create a new closed date for a location' );
354 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 114, 'DELETE_NON_CAT_TYPE',
355      'Allow a user to delete a non cataloged type' );
356 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 115, 'money.collections_tracker.create',
357      'Allow a user to put someone into collections' );
358 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 116, 'money.collections_tracker.delete',
359      'Allow a user to remove someone from collections' );
360 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 117, 'BAR_PATRON',
361      'Allow a user to bar a patron' );
362 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 118, 'UNBAR_PATRON',
363      'Allow a user to un-bar a patron' );
364 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 119, 'DELETE_WORKSTATION',
365      'Allow a user to remove an existing workstation so a new one can replace it' );
366 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 120, 'group_application.user',
367      'Allow a user to add/remove users to/from the "User" group' );
368 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 121, 'group_application.user.patron',
369      'Allow a user to add/remove users to/from the "Patron" group' );
370 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 122, 'group_application.user.staff',
371      'Allow a user to add/remove users to/from the "Staff" group' );
372 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 123, 'group_application.user.staff.circ',
373      'Allow a user to add/remove users to/from the "Circulator" group' );
374 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 124, 'group_application.user.staff.cat',
375      'Allow a user to add/remove users to/from the "Cataloger" group' );
376 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 125, 'group_application.user.staff.admin.global_admin',
377      'Allow a user to add/remove users to/from the "GlobalAdmin" group' );
378 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 126, 'group_application.user.staff.admin.local_admin',
379      'Allow a user to add/remove users to/from the "LocalAdmin" group' );
380 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 127, 'group_application.user.staff.admin.lib_manager',
381      'Allow a user to add/remove users to/from the "LibraryManager" group' );
382 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 128, 'group_application.user.staff.cat.cat1',
383      'Allow a user to add/remove users to/from the "Cat1" group' );
384 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 129, 'group_application.user.staff.supercat',
385      'Allow a user to add/remove users to/from the "Supercat" group' );
386 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 130, 'group_application.user.sip_client',
387      'Allow a user to add/remove users to/from the "SIP-Client" group' );
388 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 131, 'group_application.user.vendor',
389      'Allow a user to add/remove users to/from the "Vendor" group' );
390 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 132, 'ITEM_AGE_PROTECTED.override',
391      'Allow a user to place a hold on an age-protected item' );
392 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 133, 'MAX_RENEWALS_REACHED.override',
393      'Allow a user to renew an item past the maximum renewal count' );
394 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 134, 'PATRON_EXCEEDS_CHECKOUT_COUNT.override',
395      'Allow staff to override checkout count failure' );
396 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 135, 'PATRON_EXCEEDS_OVERDUE_COUNT.override',
397      'Allow staff to override overdue count failure' );
398 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 136, 'PATRON_EXCEEDS_FINES.override',
399      'Allow staff to override fine amount checkout failure' );
400 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 137, 'CIRC_EXCEEDS_COPY_RANGE.override',
401      'Allow staff to override circulation copy range failure' );
402 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 138, 'ITEM_ON_HOLDS_SHELF.override',
403      'Allow staff to override item on holds shelf failure' );
404 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 139, 'COPY_NOT_AVAILABLE.override',
405      'Allow staff to force checkout of Missing/Lost type items' );
406 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 140, 'HOLD_EXISTS.override',
407      'Allow a user to place multiple holds on a single title' );
408 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 141, 'RUN_REPORTS',
409      'Allow a user to run reports' );
410 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 142, 'SHARE_REPORT_FOLDER',
411      'Allow a user to share report his own folders' );
412 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 143, 'VIEW_REPORT_OUTPUT',
413      'Allow a user to view report output' );
414 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 144, 'COPY_CIRC_NOT_ALLOWED.override',
415      'Allow a user to checkout an item that is marked as non-circ' );
416 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 145, 'DELETE_CONTAINER_ITEM',
417      'Allow a user to delete an item out of another user''s container' );
418 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 146, 'ASSIGN_WORK_ORG_UNIT',
419      'Allow a staff member to define where another staff member has their permissions' );
420 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 147, 'CREATE_FUNDING_SOURCE',
421      'Allow a user to create a new funding source' );
422 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 148, 'DELETE_FUNDING_SOURCE',
423      'Allow a user to delete a funding source' );
424 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 149, 'VIEW_FUNDING_SOURCE',
425      'Allow a user to view a funding source' );
426 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 150, 'UPDATE_FUNDING_SOURCE',
427      'Allow a user to update a funding source' );
428 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 151, 'CREATE_FUND',
429      'Allow a user to create a new fund' );
430 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 152, 'DELETE_FUND',
431      'Allow a user to delete a fund' );
432 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 153, 'VIEW_FUND',
433      'Allow a user to view a fund' );
434 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 154, 'UPDATE_FUND',
435      'Allow a user to update a fund' );
436 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 155, 'CREATE_FUND_ALLOCATION',
437      'Allow a user to create a new fund allocation' );
438 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 156, 'DELETE_FUND_ALLOCATION',
439      'Allow a user to delete a fund allocation' );
440 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 157, 'VIEW_FUND_ALLOCATION',
441      'Allow a user to view a fund allocation' );
442 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 158, 'UPDATE_FUND_ALLOCATION',
443      'Allow a user to update a fund allocation' );
444 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 159, 'GENERAL_ACQ',
445      'Lowest level permission required to access the ACQ interface' );
446 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 160, 'CREATE_PROVIDER',
447      'Allow a user to create a new provider' );
448 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 161, 'DELETE_PROVIDER',
449      'Allow a user to delate a provider' );
450 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 162, 'VIEW_PROVIDER',
451      'Allow a user to view a provider' );
452 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 163, 'UPDATE_PROVIDER',
453      'Allow a user to update a provider' );
454 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 164, 'ADMIN_FUNDING_SOURCE',
455      'Allow a user to create/view/update/delete a funding source' );
456 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 165, 'ADMIN_FUND',
457      '(Deprecated) Allow a user to create/view/update/delete a fund' );
458 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 166, 'MANAGE_FUNDING_SOURCE',
459      'Allow a user to view/credit/debit a funding source' );
460 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 167, 'MANAGE_FUND',
461      'Allow a user to view/credit/debit a fund' );
462 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 168, 'CREATE_PICKLIST',
463      'Allows a user to create a picklist' );
464 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 169, 'ADMIN_PROVIDER',
465      'Allow a user to create/view/update/delete a provider' );
466 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 170, 'MANAGE_PROVIDER',
467      'Allow a user to view and purchase from a provider' );
468 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 171, 'VIEW_PICKLIST',
469      'Allow a user to view another users picklist' );
470 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 172, 'DELETE_RECORD',
471      'Allow a staff member to directly remove a bibliographic record' );
472 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 173, 'ADMIN_CURRENCY_TYPE',
473      'Allow a user to create/view/update/delete a currency_type' );
474 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 174, 'MARK_BAD_DEBT',
475      'Allow a user to mark a transaction as bad (unrecoverable) debt' );
476 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 175, 'VIEW_BILLING_TYPE',
477      'Allow a user to view billing types' );
478 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 176, 'MARK_ITEM_AVAILABLE',
479      'Allow a user to mark an item status as ''available''' );
480 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 177, 'MARK_ITEM_CHECKED_OUT',
481      'Allow a user to mark an item status as ''checked out''' );
482 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 178, 'MARK_ITEM_BINDERY',
483      'Allow a user to mark an item status as ''bindery''' );
484 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 179, 'MARK_ITEM_LOST',
485      'Allow a user to mark an item status as ''lost''' );
486 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 180, 'MARK_ITEM_MISSING',
487      'Allow a user to mark an item status as ''missing''' );
488 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 181, 'MARK_ITEM_IN_PROCESS',
489      'Allow a user to mark an item status as ''in process''' );
490 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 182, 'MARK_ITEM_IN_TRANSIT',
491      'Allow a user to mark an item status as ''in transit''' );
492 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 183, 'MARK_ITEM_RESHELVING',
493      'Allow a user to mark an item status as ''reshelving''' );
494 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 184, 'MARK_ITEM_ON_HOLDS_SHELF',
495      'Allow a user to mark an item status as ''on holds shelf''' );
496 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 185, 'MARK_ITEM_ON_ORDER',
497      'Allow a user to mark an item status as ''on order''' );
498 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 186, 'MARK_ITEM_ILL',
499      'Allow a user to mark an item status as ''inter-library loan''' );
500 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 187, 'group_application.user.staff.acq',
501      'Allows a user to add/remove/edit users in the "ACQ" group' );
502 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 188, 'CREATE_PURCHASE_ORDER',
503      'Allows a user to create a purchase order' );
504 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 189, 'VIEW_PURCHASE_ORDER',
505      'Allows a user to view a purchase order' );
506 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 190, 'IMPORT_ACQ_LINEITEM_BIB_RECORD',
507      'Allows a user to import a bib record from the acq staging area (on-order record) into the ILS bib data set' );
508 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 191, 'RECEIVE_PURCHASE_ORDER',
509      'Allows a user to mark a purchase order, lineitem, or individual copy as received' );
510 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 192, 'VIEW_ORG_SETTINGS',
511      'Allows a user to view all org settings at the specified level' );
512 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 193, 'CREATE_MFHD_RECORD',
513      'Allows a user to create a new MFHD record' );
514 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 194, 'UPDATE_MFHD_RECORD',
515      'Allows a user to update an MFHD record' );
516 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 195, 'DELETE_MFHD_RECORD',
517      'Allows a user to delete an MFHD record' );
518 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 196, 'ADMIN_ACQ_FUND',
519      'Allow a user to create/view/update/delete a fund' );
520 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 197, 'group_application.user.staff.acq_admin',
521      'Allows a user to add/remove/edit users in the "Acquisitions Administrators" group' );
522 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 198, 'SET_CIRC_CLAIMS_RETURNED.override',
523      'Allows staff to override the max claims returned value for a patron' );
524 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 199, 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
525      'Allows staff to manually change a patron''s claims returned count' );
526 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 200, 'UPDATE_BILL_NOTE',
527      'Allows staff to edit the note for a bill on a transaction' );
528 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 201, 'UPDATE_PAYMENT_NOTE',
529      'Allows staff to edit the note for a payment on a transaction' );
530 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 202, 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
531      'Allows staff to manually change a patron''s claims never checkout out count' );
532 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 203, 'ADMIN_COPY_LOCATION_ORDER',
533      'Allow a user to create/view/update/delete a copy location order' );
534 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 204, 'ASSIGN_GROUP_PERM',
535      '' );
536 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 205, 'CREATE_AUDIENCE',
537      '' );
538 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 206, 'CREATE_BIB_LEVEL',
539      '' );
540 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 207, 'CREATE_CIRC_DURATION',
541      '' );
542 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 208, 'CREATE_CIRC_MOD',
543      '' );
544 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 209, 'CREATE_COPY_STATUS',
545      '' );
546 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 210, 'CREATE_HOURS_OF_OPERATION',
547      '' );
548 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 211, 'CREATE_ITEM_FORM',
549      '' );
550 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 212, 'CREATE_ITEM_TYPE',
551      '' );
552 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 213, 'CREATE_LANGUAGE',
553      '' );
554 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 214, 'CREATE_LASSO',
555      '' );
556 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 215, 'CREATE_LASSO_MAP',
557      '' );
558 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 216, 'CREATE_LIT_FORM',
559      '' );
560 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 217, 'CREATE_METABIB_FIELD',
561      '' );
562 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 218, 'CREATE_NET_ACCESS_LEVEL',
563      '' );
564 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 219, 'CREATE_ORG_ADDRESS',
565      '' );
566 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 220, 'CREATE_ORG_TYPE',
567      '' );
568 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 221, 'CREATE_ORG_UNIT',
569      '' );
570 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 222, 'CREATE_ORG_UNIT_CLOSING',
571      '' );
572 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 223, 'CREATE_PERM',
573      '' );
574 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 224, 'CREATE_RELEVANCE_ADJUSTMENT',
575      '' );
576 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 225, 'CREATE_SURVEY',
577      '' );
578 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 226, 'CREATE_VR_FORMAT',
579      '' );
580 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 227, 'CREATE_XML_TRANSFORM',
581      '' );
582 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 228, 'DELETE_AUDIENCE',
583      '' );
584 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 229, 'DELETE_BIB_LEVEL',
585      '' );
586 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 230, 'DELETE_CIRC_DURATION',
587      '' );
588 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 231, 'DELETE_CIRC_MOD',
589      '' );
590 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 232, 'DELETE_COPY_STATUS',
591      '' );
592 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 233, 'DELETE_HOURS_OF_OPERATION',
593      '' );
594 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 234, 'DELETE_ITEM_FORM',
595      '' );
596 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 235, 'DELETE_ITEM_TYPE',
597      '' );
598 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 236, 'DELETE_LANGUAGE',
599      '' );
600 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 237, 'DELETE_LASSO',
601      '' );
602 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 238, 'DELETE_LASSO_MAP',
603      '' );
604 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 239, 'DELETE_LIT_FORM',
605      '' );
606 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 240, 'DELETE_METABIB_FIELD',
607      '' );
608 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 241, 'DELETE_NET_ACCESS_LEVEL',
609      '' );
610 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 242, 'DELETE_ORG_ADDRESS',
611      '' );
612 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 243, 'DELETE_ORG_TYPE',
613      '' );
614 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 244, 'DELETE_ORG_UNIT',
615      '' );
616 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 245, 'DELETE_ORG_UNIT_CLOSING',
617      '' );
618 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 246, 'DELETE_PERM',
619      '' );
620 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 247, 'DELETE_RELEVANCE_ADJUSTMENT',
621      '' );
622 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 248, 'DELETE_SURVEY',
623      '' );
624 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 249, 'DELETE_TRANSIT',
625      '' );
626 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 250, 'DELETE_VR_FORMAT',
627      '' );
628 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 251, 'DELETE_XML_TRANSFORM',
629      '' );
630 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 252, 'REMOVE_GROUP_PERM',
631      '' );
632 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 253, 'TRANSIT_COPY',
633      '' );
634 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 254, 'UPDATE_AUDIENCE',
635      '' );
636 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 255, 'UPDATE_BIB_LEVEL',
637      '' );
638 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 256, 'UPDATE_CIRC_DURATION',
639      '' );
640 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 257, 'UPDATE_CIRC_MOD',
641      '' );
642 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 258, 'UPDATE_COPY_NOTE',
643      '' );
644 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 259, 'UPDATE_COPY_STATUS',
645      '' );
646 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 260, 'UPDATE_GROUP_PERM',
647      '' );
648 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 261, 'UPDATE_HOURS_OF_OPERATION',
649      '' );
650 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 262, 'UPDATE_ITEM_FORM',
651      '' );
652 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 263, 'UPDATE_ITEM_TYPE',
653      '' );
654 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 264, 'UPDATE_LANGUAGE',
655      '' );
656 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 265, 'UPDATE_LASSO',
657      '' );
658 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 266, 'UPDATE_LASSO_MAP',
659      '' );
660 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 267, 'UPDATE_LIT_FORM',
661      '' );
662 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 268, 'UPDATE_METABIB_FIELD',
663      '' );
664 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 269, 'UPDATE_NET_ACCESS_LEVEL',
665      '' );
666 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 270, 'UPDATE_ORG_ADDRESS',
667      '' );
668 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 271, 'UPDATE_ORG_TYPE',
669      '' );
670 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 272, 'UPDATE_ORG_UNIT_CLOSING',
671      '' );
672 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 273, 'UPDATE_PERM',
673      '' );
674 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 274, 'UPDATE_RELEVANCE_ADJUSTMENT',
675      '' );
676 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 275, 'UPDATE_SURVEY',
677      '' );
678 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 276, 'UPDATE_TRANSIT',
679      '' );
680 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 277, 'UPDATE_VOLUME_NOTE',
681      '' );
682 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 278, 'UPDATE_VR_FORMAT',
683      '' );
684 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 279, 'UPDATE_XML_TRANSFORM',
685      '' );
686 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 280, 'MERGE_BIB_RECORDS',
687      '' );
688 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 281, 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
689      '' );
690 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 282, 'CREATE_ACQ_FUNDING_SOURCE',
691      '' );
692 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 283, 'CREATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
693      '' );
694 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 284, 'CREATE_AUTHORITY_IMPORT_QUEUE',
695      '' );
696 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 285, 'CREATE_AUTHORITY_RECORD_NOTE',
697      '' );
698 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 286, 'CREATE_BIB_IMPORT_FIELD_DEF',
699      '' );
700 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 287, 'CREATE_BIB_IMPORT_QUEUE',
701      '' );
702 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 288, 'CREATE_LOCALE',
703      '' );
704 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 289, 'CREATE_MARC_CODE',
705      '' );
706 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 290, 'CREATE_TRANSLATION',
707      '' );
708 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 291, 'DELETE_ACQ_FUNDING_SOURCE',
709      '' );
710 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 292, 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
711      '' );
712 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 293, 'DELETE_AUTHORITY_IMPORT_QUEUE',
713      '' );
714 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 294, 'DELETE_AUTHORITY_RECORD_NOTE',
715      '' );
716 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 295, 'DELETE_BIB_IMPORT_IMPORT_FIELD_DEF',
717      '' );
718 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 296, 'DELETE_BIB_IMPORT_QUEUE',
719      '' );
720 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 297, 'DELETE_LOCALE',
721      '' );
722 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 298, 'DELETE_MARC_CODE',
723      '' );
724 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 299, 'DELETE_TRANSLATION',
725      '' );
726 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 300, 'UPDATE_ACQ_FUNDING_SOURCE',
727      '' );
728 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 301, 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
729      '' );
730 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 302, 'UPDATE_AUTHORITY_IMPORT_QUEUE',
731      '' );
732 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 303, 'UPDATE_AUTHORITY_RECORD_NOTE',
733      '' );
734 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 304, 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
735      '' );
736 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 305, 'UPDATE_BIB_IMPORT_QUEUE',
737      '' );
738 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 306, 'UPDATE_LOCALE',
739      '' );
740 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 307, 'UPDATE_MARC_CODE',
741      '' );
742 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 308, 'UPDATE_TRANSLATION',
743      '' );
744 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 309, 'VIEW_ACQ_FUNDING_SOURCE',
745      '' );
746 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 310, 'VIEW_AUTHORITY_RECORD_NOTES',
747      '' );
748 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 311, 'CREATE_IMPORT_ITEM',
749      '' );
750 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 312, 'CREATE_IMPORT_ITEM_ATTR_DEF',
751      '' );
752 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 313, 'CREATE_IMPORT_TRASH_FIELD',
753      '' );
754 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 314, 'DELETE_IMPORT_ITEM',
755      '' );
756 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 315, 'DELETE_IMPORT_ITEM_ATTR_DEF',
757      '' );
758 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 316, 'DELETE_IMPORT_TRASH_FIELD',
759      '' );
760 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 317, 'UPDATE_IMPORT_ITEM',
761      '' );
762 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 318, 'UPDATE_IMPORT_ITEM_ATTR_DEF',
763      '' );
764 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 319, 'UPDATE_IMPORT_TRASH_FIELD',
765      '' );
766 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 320, 'UPDATE_ORG_UNIT_SETTING_ALL',
767      '' );
768 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 321, 'UPDATE_ORG_UNIT_SETTING.circ.lost_materials_processing_fee',
769      '' );
770 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 322, 'UPDATE_ORG_UNIT_SETTING.cat.default_item_price',
771      '' );
772 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 323, 'UPDATE_ORG_UNIT_SETTING.auth.opac_timeout',
773      '' );
774 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 324, 'UPDATE_ORG_UNIT_SETTING.auth.staff_timeout',
775      '' );
776 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 325, 'UPDATE_ORG_UNIT_SETTING.org.bounced_emails',
777      '' );
778 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 326, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_alert_interval',
779      '' );
780 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 327, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_interval',
781      '' );
782 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 328, 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow',
783      '' );
784 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 329, 'UPDATE_ORG_UNIT_SETTING.circ.void_overdue_on_lost',
785      '' );
786 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 330, 'UPDATE_ORG_UNIT_SETTING.circ.hold_stalling.soft',
787      '' );
788 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 331, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.hard',
789      '' );
790 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 332, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.soft',
791      '' );
792 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 333, 'UPDATE_ORG_UNIT_SETTING.opac.barcode_regex',
793      '' );
794 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 334, 'UPDATE_ORG_UNIT_SETTING.global.password_regex',
795      '' );
796 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 335, 'UPDATE_ORG_UNIT_SETTING.circ.item_checkout_history.max',
797      '' );
798 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 336, 'UPDATE_ORG_UNIT_SETTING.circ.reshelving_complete.interval',
799      '' );
800 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 337, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.patron_login_timeout',
801      '' );
802 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 338, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.alert_on_checkout_event',
803      '' );
804 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 339, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.require_patron_password',
805      '' );
806 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 340, 'UPDATE_ORG_UNIT_SETTING.global.juvenile_age_threshold',
807      '' );
808 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 341, 'UPDATE_ORG_UNIT_SETTING.cat.bib.keep_on_empty',
809      '' );
810 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 342, 'UPDATE_ORG_UNIT_SETTING.cat.bib.alert_on_empty',
811      '' );
812 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 343, 'UPDATE_ORG_UNIT_SETTING.patron.password.use_phone',
813      '' );
814 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 344, 'HOLD_ITEM_CHECKED_OUT.override',
815      'Allows a user to place a hold on an item that they already have checked out' );
816 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 345, 'ADMIN_ACQ_CANCEL_CAUSE',
817      'Allow a user to create/update/delete reasons for order cancellations' );
818 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 346, 'ACQ_XFER_MANUAL_DFUND_AMOUNT',
819      'Allow a user to transfer different amounts of money out of one fund and into another' );
820 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 347, 'OVERRIDE_HOLD_HAS_LOCAL_COPY',
821      'Allow a user to override the circ.holds.hold_has_copy_at.block setting' );
822 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 348, 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
823      'Allow a user to change the pickup and transit destination for a captured hold item already in transit' );
824 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 349, 'COPY_NEEDED_FOR_HOLD.override',
825      'Allow a user to force renewal of an item that could fulfill a hold request' );
826 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 350, 'MERGE_AUTH_RECORDS',
827      'Allow a user to merge authority records together' );
828 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 351, 'ALLOW_ALT_TCN',
829      'Allows staff to import a record using an alternate TCN to avoid conflicts' );
830 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 352, 'ADMIN_TRIGGER_EVENT_DEF',
831      'Allow a user to administer trigger event definitions' );
832 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 353, 'ADMIN_TRIGGER_CLEANUP',
833      'Allow a user to create, delete, and update trigger cleanup entries' );
834 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 354, 'CREATE_TRIGGER_CLEANUP',
835      'Allow a user to create trigger cleanup entries' );
836 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 355, 'DELETE_TRIGGER_CLEANUP',
837      'Allow a user to delete trigger cleanup entries' );
838 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 356, 'UPDATE_TRIGGER_CLEANUP',
839      'Allow a user to update trigger cleanup entries' );
840 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 357, 'CREATE_TRIGGER_EVENT_DEF',
841      'Allow a user to create trigger event definitions' );
842 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 358, 'DELETE_TRIGGER_EVENT_DEF',
843      'Allow a user to delete trigger event definitions' );
844 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 359, 'UPDATE_TRIGGER_EVENT_DEF',
845      'Allow a user to update trigger event definitions' );
846 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 360, 'VIEW_TRIGGER_EVENT_DEF',
847      'Allow a user to view trigger event definitions' );
848 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 361, 'ADMIN_TRIGGER_HOOK',
849      'Allow a user to create, update, and delete trigger hooks' );
850 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 362, 'CREATE_TRIGGER_HOOK',
851      'Allow a user to create trigger hooks' );
852 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 363, 'DELETE_TRIGGER_HOOK',
853      'Allow a user to delete trigger hooks' );
854 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 364, 'UPDATE_TRIGGER_HOOK',
855      'Allow a user to update trigger hooks' );
856 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 365, 'ADMIN_TRIGGER_REACTOR',
857      'Allow a user to create, update, and delete trigger reactors' );
858 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 366, 'CREATE_TRIGGER_REACTOR',
859      'Allow a user to create trigger reactors' );
860 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 367, 'DELETE_TRIGGER_REACTOR',
861      'Allow a user to delete trigger reactors' );
862 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 368, 'UPDATE_TRIGGER_REACTOR',
863      'Allow a user to update trigger reactors' );
864 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 369, 'ADMIN_TRIGGER_TEMPLATE_OUTPUT',
865      'Allow a user to delete trigger template output' );
866 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 370, 'DELETE_TRIGGER_TEMPLATE_OUTPUT',
867      'Allow a user to delete trigger template output' );
868 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 371, 'ADMIN_TRIGGER_VALIDATOR',
869      'Allow a user to create, update, and delete trigger validators' );
870 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 372, 'CREATE_TRIGGER_VALIDATOR',
871      'Allow a user to create trigger validators' );
872 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 373, 'DELETE_TRIGGER_VALIDATOR',
873      'Allow a user to delete trigger validators' );
874 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 374, 'UPDATE_TRIGGER_VALIDATOR',
875      'Allow a user to update trigger validators' );
876 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 375, 'HOLD_LOCAL_AVAIL_OVERRIDE',
877      'Allow a user to place a hold despite the availability of a local copy' );
878 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 376, 'ADMIN_BOOKING_RESOURCE',
879      'Enables the user to create/update/delete booking resources' );
880 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 377, 'ADMIN_BOOKING_RESOURCE_TYPE',
881      'Enables the user to create/update/delete booking resource types' );
882 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 378, 'ADMIN_BOOKING_RESOURCE_ATTR',
883      'Enables the user to create/update/delete booking resource attributes' );
884 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 379, 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
885      'Enables the user to create/update/delete booking resource attribute maps' );
886 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 380, 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
887      'Enables the user to create/update/delete booking resource attribute values' );
888 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 381, 'ADMIN_BOOKING_RESERVATION',
889      'Enables the user to create/update/delete booking reservations' );
890 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 382, 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
891      'Enables the user to create/update/delete booking reservation attribute value maps' );
892 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 383, 'RETRIEVE_RESERVATION_PULL_LIST',
893      'Allows a user to retrieve a booking reservation pull list' );
894 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 384, 'CAPTURE_RESERVATION',
895      'Allows a user to capture booking reservations' );
896 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 385, 'UPDATE_RECORD',
897      '' );
898 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 386, 'UPDATE_ORG_UNIT_SETTING.circ.block_renews_for_holds',
899      '' );
900 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 387, 'MERGE_USERS',
901      'Allows user records to be merged' );
902 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 388, 'ISSUANCE_HOLDS',
903      'Allow a user to place holds on serials issuances' );
904 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 389, 'VIEW_CREDIT_CARD_PROCESSING',
905      'View org unit settings related to credit card processing' );
906 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 390, 'ADMIN_CREDIT_CARD_PROCESSING',
907      'Update org unit settings related to credit card processing' );
908 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 391, 'ADMIN_SERIAL_CAPTION_PATTERN',
909         'Create/update/delete serial caption and pattern objects' );
910 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 392, 'ADMIN_SERIAL_SUBSCRIPTION',
911         'Create/update/delete serial subscription objects' );
912 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 393, 'ADMIN_SERIAL_DISTRIBUTION',
913         'Create/update/delete serial distribution objects' );
914 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 394, 'ADMIN_SERIAL_STREAM',
915         'Create/update/delete serial stream objects' );
916 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 395, 'RECEIVE_SERIAL',
917         'Receive serial items' );
918
919 -- Now for the permissions from the IDL.  We don't have descriptions for them.
920
921 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 396, 'ADMIN_ACQ_CLAIM' );
922 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 397, 'ADMIN_ACQ_CLAIM_EVENT_TYPE' );
923 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 398, 'ADMIN_ACQ_CLAIM_TYPE' );
924 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 399, 'ADMIN_ACQ_DISTRIB_FORMULA' );
925 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 400, 'ADMIN_ACQ_FISCAL_YEAR' );
926 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 401, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT' );
927 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 402, 'ADMIN_ACQ_FUND_TAG' );
928 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 403, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT' );
929 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 404, 'ADMIN_AGE_PROTECT_RULE' );
930 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 405, 'ADMIN_ASSET_COPY_TEMPLATE' );
931 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 406, 'ADMIN_BOOKING_RESERVATION_ATTR_MAP' );
932 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 407, 'ADMIN_CIRC_MATRIX_MATCHPOINT' );
933 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 408, 'ADMIN_CIRC_MOD' );
934 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 409, 'ADMIN_CLAIM_POLICY' );
935 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 410, 'ADMIN_CONFIG_REMOTE_ACCOUNT' );
936 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 411, 'ADMIN_FIELD_DOC' );
937 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 412, 'ADMIN_GLOBAL_FLAG' );
938 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 413, 'ADMIN_GROUP_PENALTY_THRESHOLD' );
939 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 414, 'ADMIN_HOLD_CANCEL_CAUSE' );
940 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 415, 'ADMIN_HOLD_MATRIX_MATCHPOINT' );
941 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 416, 'ADMIN_IDENT_TYPE' );
942 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 417, 'ADMIN_IMPORT_ITEM_ATTR_DEF' );
943 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 418, 'ADMIN_INDEX_NORMALIZER' );
944 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 419, 'ADMIN_INVOICE' );
945 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 420, 'ADMIN_INVOICE_METHOD' );
946 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 421, 'ADMIN_INVOICE_PAYMENT_METHOD' );
947 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 422, 'ADMIN_LINEITEM_MARC_ATTR_DEF' );
948 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 423, 'ADMIN_MARC_CODE' );
949 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 424, 'ADMIN_MAX_FINE_RULE' );
950 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 425, 'ADMIN_MERGE_PROFILE' );
951 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 426, 'ADMIN_ORG_UNIT_SETTING_TYPE' );
952 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 427, 'ADMIN_RECURRING_FINE_RULE' );
953 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 428, 'ADMIN_STANDING_PENALTY' );
954 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 429, 'ADMIN_SURVEY' );
955 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 430, 'ADMIN_USER_REQUEST_TYPE' );
956 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 431, 'ADMIN_USER_SETTING_GROUP' );
957 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 432, 'ADMIN_USER_SETTING_TYPE' );
958 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 433, 'ADMIN_Z3950_SOURCE' );
959 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 434, 'CREATE_BIB_BTYPE' );
960 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 435, 'CREATE_BIBLIO_FINGERPRINT' );
961 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 436, 'CREATE_BIB_SOURCE' );
962 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 437, 'CREATE_BILLING_TYPE' );
963 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 438, 'CREATE_CN_BTYPE' );
964 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 439, 'CREATE_COPY_BTYPE' );
965 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 440, 'CREATE_INVOICE' );
966 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 441, 'CREATE_INVOICE_ITEM_TYPE' );
967 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 442, 'CREATE_INVOICE_METHOD' );
968 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 443, 'CREATE_MERGE_PROFILE' );
969 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 444, 'CREATE_METABIB_CLASS' );
970 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 445, 'CREATE_METABIB_SEARCH_ALIAS' );
971 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 446, 'CREATE_USER_BTYPE' );
972 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 447, 'DELETE_BIB_BTYPE' );
973 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 448, 'DELETE_BIBLIO_FINGERPRINT' );
974 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 449, 'DELETE_BIB_SOURCE' );
975 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 450, 'DELETE_BILLING_TYPE' );
976 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 451, 'DELETE_CN_BTYPE' );
977 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 452, 'DELETE_COPY_BTYPE' );
978 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 453, 'DELETE_INVOICE_ITEM_TYPE' );
979 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 454, 'DELETE_INVOICE_METHOD' );
980 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 455, 'DELETE_MERGE_PROFILE' );
981 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 456, 'DELETE_METABIB_CLASS' );
982 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 457, 'DELETE_METABIB_SEARCH_ALIAS' );
983 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 458, 'DELETE_USER_BTYPE' );
984 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 459, 'MANAGE_CLAIM' );
985 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 460, 'UPDATE_BIB_BTYPE' );
986 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 461, 'UPDATE_BIBLIO_FINGERPRINT' );
987 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 462, 'UPDATE_BIB_SOURCE' );
988 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 463, 'UPDATE_BILLING_TYPE' );
989 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 464, 'UPDATE_CN_BTYPE' );
990 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 465, 'UPDATE_COPY_BTYPE' );
991 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 466, 'UPDATE_INVOICE_ITEM_TYPE' );
992 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 467, 'UPDATE_INVOICE_METHOD' );
993 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 468, 'UPDATE_MERGE_PROFILE' );
994 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 469, 'UPDATE_METABIB_CLASS' );
995 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 470, 'UPDATE_METABIB_SEARCH_ALIAS' );
996 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 471, 'UPDATE_USER_BTYPE' );
997 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 472, 'user_request.create' );
998 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 473, 'user_request.delete' );
999 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 474, 'user_request.update' );
1000 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 475, 'user_request.view' );
1001 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 476, 'VIEW_ACQ_FUND_ALLOCATION_PERCENT' );
1002 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 477, 'VIEW_CIRC_MATRIX_MATCHPOINT' );
1003 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 478, 'VIEW_CLAIM' );
1004 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 479, 'VIEW_GROUP_PENALTY_THRESHOLD' );
1005 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 480, 'VIEW_HOLD_MATRIX_MATCHPOINT' );
1006 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 481, 'VIEW_INVOICE' );
1007 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 482, 'VIEW_MERGE_PROFILE' );
1008 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 483, 'VIEW_SERIAL_SUBSCRIPTION' );
1009 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 484, 'VIEW_STANDING_PENALTY' );
1010
1011 -- For every permission in the temp_perm table that has a matching
1012 -- permission in the real table: record the original id.
1013
1014 UPDATE permission.temp_perm AS tp
1015 SET old_id =
1016         (
1017                 SELECT id
1018                 FROM permission.perm_list AS ppl
1019                 WHERE ppl.code = tp.code
1020         )
1021 WHERE code IN ( SELECT code FROM permission.perm_list );
1022
1023 -- Start juggling ids.
1024
1025 -- If any permissions have negative ids (with the special exception of -1),
1026 -- we need to move them into the positive range in order to avoid duplicate
1027 -- key problems (since we are going to use the negative range as a temporary
1028 -- staging area).
1029
1030 -- First, move any predefined permissions that have negative ids (again with
1031 -- the special exception of -1).  Temporarily give them positive ids based on
1032 -- the sequence.
1033
1034 UPDATE permission.perm_list
1035 SET id = NEXTVAL('permission.perm_list_id_seq'::regclass)
1036 WHERE id < -1
1037   AND code IN (SELECT code FROM permission.temp_perm);
1038
1039 -- Identify any non-predefined permissions whose ids are either negative
1040 -- or within the range (0-1000) reserved for predefined permissions.
1041 -- Assign them ids above 1000, based on the sequence.  Record the new
1042 -- ids in the temp_perm table.
1043
1044 INSERT INTO permission.temp_perm ( id, code, description, old_id, predefined )
1045 (
1046         SELECT NEXTVAL('permission.perm_list_id_seq'::regclass),
1047                 code, description, id, false
1048         FROM permission.perm_list
1049         WHERE  ( id < -1 OR id BETWEEN 0 AND 1000 )
1050         AND code NOT IN (SELECT code FROM permission.temp_perm)
1051 );
1052
1053 -- Now update the ids of those non-predefined permissions, using the
1054 -- values assigned in the previous step.
1055
1056 UPDATE permission.perm_list AS ppl
1057 SET id = (
1058                 SELECT id
1059                 FROM permission.temp_perm AS tp
1060                 WHERE tp.code = ppl.code
1061         )
1062 WHERE id IN ( SELECT old_id FROM permission.temp_perm WHERE NOT predefined );
1063
1064 -- Now the negative ids have been eliminated, except for -1.  Move all the
1065 -- predefined permissions temporarily into the negative range.
1066
1067 UPDATE permission.perm_list
1068 SET id = -1 - id
1069 WHERE id <> -1
1070 AND code IN ( SELECT code from permission.temp_perm WHERE predefined );
1071
1072 -- Apply the final ids to the existing predefined permissions.
1073
1074 UPDATE permission.perm_list AS ppl
1075 SET id =
1076         (
1077                 SELECT id
1078                 FROM permission.temp_perm AS tp
1079                 WHERE tp.code = ppl.code
1080         )
1081 WHERE
1082         id <> -1
1083         AND ppl.code IN
1084         (
1085                 SELECT code from permission.temp_perm
1086                 WHERE predefined
1087                 AND old_id IS NOT NULL
1088         );
1089
1090 -- If there are any predefined permissions that don't exist yet in
1091 -- permission.perm_list, insert them now.
1092
1093 INSERT INTO permission.perm_list ( id, code, description )
1094 (
1095         SELECT id, code, description
1096         FROM permission.temp_perm
1097         WHERE old_id IS NULL
1098 );
1099
1100 -- Reset the sequence to the lowest feasible value.  This may or may not
1101 -- accomplish anything, but it will do no harm.
1102
1103 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, GREATEST( 
1104         (SELECT MAX(id) FROM permission.perm_list), 1000 ));
1105
1106 -- If any permission lacks a description, use the code as a description.
1107 -- It's better than nothing.
1108
1109 UPDATE permission.perm_list
1110 SET description = code
1111 WHERE description IS NULL
1112    OR description = '';
1113
1114 -- Thus endeth the Great Renumbering.
1115
1116 -- Having massaged the permissions, massage the way they are assigned, by inserting
1117 -- rows into permission.grp_perm_map.  Some of these permissions may have already
1118 -- been assigned, so we insert the rows only if they aren't already there.
1119
1120 -- for backwards compat, give everyone the permission
1121 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1122     SELECT 1, id, 0, false FROM permission.perm_list AS perm
1123         WHERE code = 'HOLD_ITEM_CHECKED_OUT.override'
1124                 AND NOT EXISTS (
1125                         SELECT 1
1126                         FROM permission.grp_perm_map AS map
1127                         WHERE
1128                                 grp = 1
1129                                 AND map.perm = perm.id
1130                 );
1131
1132 -- Add trigger administration permissions to the Local System Administrator group.
1133 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1134     SELECT 10, id, 1, false FROM permission.perm_list AS perm
1135     WHERE (
1136                 perm.code LIKE 'ADMIN_TRIGGER%'
1137         OR perm.code LIKE 'CREATE_TRIGGER%'
1138         OR perm.code LIKE 'DELETE_TRIGGER%'
1139         OR perm.code LIKE 'UPDATE_TRIGGER%'
1140         ) AND NOT EXISTS (
1141                 SELECT 1
1142                 FROM permission.grp_perm_map AS map
1143                 WHERE
1144                         grp = 10
1145                         AND map.perm = perm.id
1146         );
1147
1148 -- View trigger permissions are required at a consortial level for initial setup
1149 -- (as before, only if the row doesn't already exist)
1150 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1151     SELECT 10, id, 0, false FROM permission.perm_list AS perm
1152         WHERE code LIKE 'VIEW_TRIGGER%'
1153                 AND NOT EXISTS (
1154                         SELECT 1
1155                         FROM permission.grp_perm_map AS map
1156                         WHERE
1157                                 grp = 10
1158                                 AND map.perm = perm.id
1159                 );
1160
1161 -- Permission for merging auth records may already be defined,
1162 -- so add it only if it isn't there.
1163 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1164     SELECT 4, id, 1, false FROM permission.perm_list AS perm
1165         WHERE code = 'MERGE_AUTH_RECORDS'
1166                 AND NOT EXISTS (
1167                         SELECT 1
1168                         FROM permission.grp_perm_map AS map
1169                         WHERE
1170                                 grp = 4
1171                                 AND map.perm = perm.id
1172                 );
1173
1174 -- Create a reference table as parent to both
1175 -- config.org_unit_setting_type and config_usr_setting_type
1176
1177 CREATE TABLE config.settings_group (
1178     name    TEXT PRIMARY KEY,
1179     label   TEXT UNIQUE NOT NULL -- I18N
1180 );
1181
1182 -- org_unit setting types
1183 CREATE TABLE config.org_unit_setting_type (
1184     name            TEXT    PRIMARY KEY,
1185     label           TEXT    UNIQUE NOT NULL,
1186     grp             TEXT    REFERENCES config.settings_group (name),
1187     description     TEXT,
1188     datatype        TEXT    NOT NULL DEFAULT 'string',
1189     fm_class        TEXT,
1190     view_perm       INT,
1191     update_perm     INT,
1192     --
1193     -- define valid datatypes
1194     --
1195     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1196     ( 'bool', 'integer', 'float', 'currency', 'interval',
1197       'date', 'string', 'object', 'array', 'link' ) ),
1198     --
1199     -- fm_class is meaningful only for 'link' datatype
1200     --
1201     CONSTRAINT coust_no_empty_link CHECK
1202     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
1203       ( datatype <> 'link' AND fm_class IS NULL ) ),
1204         CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id)
1205                 ON UPDATE CASCADE
1206                 ON DELETE RESTRICT
1207                 DEFERRABLE INITIALLY DEFERRED,
1208         CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id)
1209                 ON UPDATE CASCADE
1210                 DEFERRABLE INITIALLY DEFERRED
1211 );
1212
1213 CREATE TABLE config.usr_setting_type (
1214
1215     name TEXT PRIMARY KEY,
1216     opac_visible BOOL NOT NULL DEFAULT FALSE,
1217     label TEXT UNIQUE NOT NULL,
1218     description TEXT,
1219     grp             TEXT    REFERENCES config.settings_group (name),
1220     datatype TEXT NOT NULL DEFAULT 'string',
1221     fm_class TEXT,
1222
1223     --
1224     -- define valid datatypes
1225     --
1226     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1227     ( 'bool', 'integer', 'float', 'currency', 'interval',
1228         'date', 'string', 'object', 'array', 'link' ) ),
1229
1230     --
1231     -- fm_class is meaningful only for 'link' datatype
1232     --
1233     CONSTRAINT coust_no_empty_link CHECK
1234     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
1235         ( datatype <> 'link' AND fm_class IS NULL ) )
1236
1237 );
1238
1239 --------------------------------------
1240 -- Seed data for org_unit_setting_type
1241 --------------------------------------
1242
1243 INSERT into config.org_unit_setting_type
1244 ( name, label, description, datatype ) VALUES
1245
1246 ( 'auth.opac_timeout',
1247   'OPAC Inactivity Timeout (in seconds)',
1248   null,
1249   'integer' ),
1250
1251 ( 'auth.staff_timeout',
1252   'Staff Login Inactivity Timeout (in seconds)',
1253   null,
1254   'integer' ),
1255
1256 ( 'circ.lost_materials_processing_fee',
1257   'Lost Materials Processing Fee',
1258   null,
1259   'currency' ),
1260
1261 ( 'cat.default_item_price',
1262   'Default Item Price',
1263   null,
1264   'currency' ),
1265
1266 ( 'org.bounced_emails',
1267   'Sending email address for patron notices',
1268   null,
1269   'string' ),
1270
1271 ( 'circ.hold_expire_alert_interval',
1272   'Holds: Expire Alert Interval',
1273   'Amount of time before a hold expires at which point the patron should be alerted',
1274   'interval' ),
1275
1276 ( 'circ.hold_expire_interval',
1277   'Holds: Expire Interval',
1278   'Amount of time after a hold is placed before the hold expires.  Example "100 days"',
1279   'interval' ),
1280
1281 ( 'credit.payments.allow',
1282   'Allow Credit Card Payments',
1283   'If enabled, patrons will be able to pay fines accrued at this location via credit card',
1284   'bool' ),
1285
1286 ( 'global.default_locale',
1287   'Global Default Locale',
1288   null,
1289   'string' ),
1290
1291 ( 'circ.void_overdue_on_lost',
1292   'Void overdue fines when items are marked lost',
1293   null,
1294   'bool' ),
1295
1296 ( 'circ.hold_stalling.soft',
1297   'Holds: Soft stalling interval',
1298   'How long to wait before allowing remote items to be opportunistically captured for a hold.  Example "5 days"',
1299   'interval' ),
1300
1301 ( 'circ.hold_stalling_hard',
1302   'Holds: Hard stalling interval',
1303   '',
1304   'interval' ),
1305
1306 ( 'circ.hold_boundary.hard',
1307   'Holds: Hard boundary',
1308   null,
1309   'integer' ),
1310
1311 ( 'circ.hold_boundary.soft',
1312   'Holds: Soft boundary',
1313   null,
1314   'integer' ),
1315
1316 ( 'opac.barcode_regex',
1317   'Patron barcode format',
1318   'Regular expression defining the patron barcode format',
1319   'string' ),
1320
1321 ( 'global.password_regex',
1322   'Password format',
1323   'Regular expression defining the password format',
1324   'string' ),
1325
1326 ( 'circ.item_checkout_history.max',
1327   'Maximum previous checkouts displayed',
1328   'This is the maximum number of previous circulations the staff client will display when investigating item details',
1329   'integer' ),
1330
1331 ( 'circ.reshelving_complete.interval',
1332   'Change reshelving status interval',
1333   'Amount of time to wait before changing an item from "reshelving" status to "available".  Examples: "1 day", "6 hours"',
1334   'interval' ),
1335
1336 ( 'circ.holds.default_estimated_wait_interval',
1337   'Holds: Default Estimated Wait',
1338   '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.',
1339   'interval' ),
1340
1341 ( 'circ.holds.min_estimated_wait_interval',
1342   'Holds: Minimum Estimated Wait',
1343   '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.',
1344   'interval' ),
1345
1346 ( 'circ.selfcheck.patron_login_timeout',
1347   'Selfcheck: Patron Login Timeout (in seconds)',
1348   'Number of seconds of inactivity before the patron is logged out of the selfcheck interface',
1349   'integer' ),
1350
1351 ( 'circ.selfcheck.alert.popup',
1352   'Selfcheck: Pop-up alert for errors',
1353   'If true, checkout/renewal errors will cause a pop-up window in addition to the on-screen message',
1354   'bool' ),
1355
1356 ( 'circ.selfcheck.require_patron_password',
1357   'Selfcheck: Require patron password',
1358   'If true, patrons will be required to enter their password in addition to their username/barcode to log into the selfcheck interface',
1359   'bool' ),
1360
1361 ( 'global.juvenile_age_threshold',
1362   'Juvenile Age Threshold',
1363   'The age at which a user is no long considered a juvenile.  For example, "18 years".',
1364   'interval' ),
1365
1366 ( 'cat.bib.keep_on_empty',
1367   'Retain empty bib records',
1368   'Retain a bib record even when all attached copies are deleted',
1369   'bool' ),
1370
1371 ( 'cat.bib.alert_on_empty',
1372   'Alert on empty bib records',
1373   'Alert staff when the last copy for a record is being deleted',
1374   'bool' ),
1375
1376 ( 'patron.password.use_phone',
1377   'Patron: password from phone #',
1378   'Use the last 4 digits of the patrons phone number as the default password when creating new users',
1379   'bool' ),
1380
1381 ( 'circ.charge_on_damaged',
1382   'Charge item price when marked damaged',
1383   'Charge item price when marked damaged',
1384   'bool' ),
1385
1386 ( 'circ.charge_lost_on_zero',
1387   'Charge lost on zero',
1388   '',
1389   'bool' ),
1390
1391 ( 'circ.damaged_item_processing_fee',
1392   'Charge processing fee for damaged items',
1393   'Charge processing fee for damaged items',
1394   'currency' ),
1395
1396 ( 'circ.void_lost_on_checkin',
1397   'Circ: Void lost item billing when returned',
1398   'Void lost item billing when returned',
1399   'bool' ),
1400
1401 ( 'circ.max_accept_return_of_lost',
1402   'Circ: Void lost max interval',
1403   'Items that have been lost this long will not result in voided billings when returned.  E.g. ''6 months''',
1404   'interval' ),
1405
1406 ( 'circ.void_lost_proc_fee_on_checkin',
1407   'Circ: Void processing fee on lost item return',
1408   'Void processing fee when lost item returned',
1409   'bool' ),
1410
1411 ( 'circ.restore_overdue_on_lost_return',
1412   'Circ: Restore overdues on lost item return',
1413   'Restore overdue fines on lost item return',
1414   'bool' ),
1415
1416 ( 'circ.lost_immediately_available',
1417   'Circ: Lost items usable on checkin',
1418   'Lost items are usable on checkin instead of going ''home'' first',
1419   'bool' ),
1420
1421 ( 'circ.holds_fifo',
1422   'Holds: FIFO',
1423   'Force holds to a more strict First-In, First-Out capture',
1424   'bool' ),
1425
1426 ( 'opac.allow_pending_address',
1427   'OPAC: Allow pending addresses',
1428   'If enabled, patrons can create and edit existing addresses.  Addresses are kept in a pending state until staff approves the changes',
1429   'bool' ),
1430
1431 ( 'ui.circ.show_billing_tab_on_bills',
1432   'Show billing tab first when bills are present',
1433   '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',
1434   'bool' ),
1435
1436 ( 'ui.general.idle_timeout',
1437     'GUI: Idle timeout',
1438     '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).',
1439     'integer' ),
1440
1441 ( 'ui.circ.in_house_use.entry_cap',
1442   'GUI: Record In-House Use: Maximum # of uses allowed per entry.',
1443   'The # of uses entry in the Record In-House Use interface may not exceed the value of this setting.',
1444   'integer' ),
1445
1446 ( 'ui.circ.in_house_use.entry_warn',
1447   'GUI: Record In-House Use: # of uses threshold for Are You Sure? dialog.',
1448   'In the Record In-House Use interface, a submission attempt will warn if the # of uses field exceeds the value of this setting.',
1449   'integer' ),
1450
1451 ( 'acq.default_circ_modifier',
1452   'Default circulation modifier',
1453   null,
1454   'string' ),
1455
1456 ( 'acq.tmp_barcode_prefix',
1457   'Temporary barcode prefix',
1458   null,
1459   'string' ),
1460
1461 ( 'acq.tmp_callnumber_prefix',
1462   'Temporary call number prefix',
1463   null,
1464   'string' ),
1465
1466 ( 'ui.circ.patron_summary.horizontal',
1467   'Patron circulation summary is horizontal',
1468   null,
1469   'bool' ),
1470
1471 ( 'ui.staff.require_initials',
1472   oils_i18n_gettext('ui.staff.require_initials', 'GUI: Require staff initials for entry/edit of item/patron/penalty notes/messages.', 'coust', 'label'),
1473   oils_i18n_gettext('ui.staff.require_initials', 'Appends staff initials and edit date into note content.', 'coust', 'description'),
1474   'bool' ),
1475
1476 ( 'ui.general.button_bar',
1477   'Button bar',
1478   null,
1479   'bool' ),
1480
1481 ( 'circ.hold_shelf_status_delay',
1482   'Hold Shelf Status Delay',
1483   '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.',
1484   'interval' ),
1485
1486 ( 'circ.patron_invalid_address_apply_penalty',
1487   'Invalid patron address penalty',
1488   'When set, if a patron address is set to invalid, a penalty is applied.',
1489   'bool' ),
1490
1491 ( 'circ.checkout_fills_related_hold',
1492   'Checkout Fills Related Hold',
1493   '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',
1494   'bool'),
1495
1496 ( 'circ.selfcheck.auto_override_checkout_events',
1497   'Selfcheck override events list',
1498   'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction',
1499   'array' ),
1500
1501 ( 'circ.staff_client.do_not_auto_attempt_print',
1502   'Disable Automatic Print Attempt Type List',
1503   '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).',
1504   'array' ),
1505
1506 ( 'ui.patron.default_inet_access_level',
1507   'Default level of patrons'' internet access',
1508   null,
1509   'integer' ),
1510
1511 ( 'circ.max_patron_claim_return_count',
1512     'Max Patron Claims Returned Count',
1513     'When this count is exceeded, a staff override is required to mark the item as claims returned',
1514     'integer' ),
1515
1516 ( 'circ.obscure_dob',
1517     'Obscure the Date of Birth field',
1518     '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.',
1519     'bool' ),
1520
1521 ( 'circ.auto_hide_patron_summary',
1522     'GUI: Toggle off the patron summary sidebar after first view.',
1523     'When true, the patron summary sidebar will collapse after a new patron sub-interface is selected.',
1524     'bool' ),
1525
1526 ( 'credit.processor.default',
1527     'Credit card processing: Name default credit processor',
1528     'This can be "AuthorizeNet", "PayPal" (for the Website Payment Pro API), or "PayflowPro".',
1529     'string' ),
1530
1531 ( 'credit.processor.authorizenet.enabled',
1532     'Credit card processing: AuthorizeNet enabled',
1533     '',
1534     'bool' ),
1535
1536 ( 'credit.processor.authorizenet.login',
1537     'Credit card processing: AuthorizeNet login',
1538     '',
1539     'string' ),
1540
1541 ( 'credit.processor.authorizenet.password',
1542     'Credit card processing: AuthorizeNet password',
1543     '',
1544     'string' ),
1545
1546 ( 'credit.processor.authorizenet.server',
1547     'Credit card processing: AuthorizeNet server',
1548     'Required if using a developer/test account with AuthorizeNet',
1549     'string' ),
1550
1551 ( 'credit.processor.authorizenet.testmode',
1552     'Credit card processing: AuthorizeNet test mode',
1553     '',
1554     'bool' ),
1555
1556 ( 'credit.processor.paypal.enabled',
1557     'Credit card processing: PayPal enabled',
1558     '',
1559     'bool' ),
1560 ( 'credit.processor.paypal.login',
1561     'Credit card processing: PayPal login',
1562     '',
1563     'string' ),
1564 ( 'credit.processor.paypal.password',
1565     'Credit card processing: PayPal password',
1566     '',
1567     'string' ),
1568 ( 'credit.processor.paypal.signature',
1569     'Credit card processing: PayPal signature',
1570     '',
1571     'string' ),
1572 ( 'credit.processor.paypal.testmode',
1573     'Credit card processing: PayPal test mode',
1574     '',
1575     'bool' ),
1576
1577 ( 'ui.admin.work_log.max_entries',
1578     oils_i18n_gettext('ui.admin.work_log.max_entries', 'GUI: Work Log: Maximum Actions Logged', 'coust', 'label'),
1579     oils_i18n_gettext('ui.admin.work_log.max_entries', 'Maximum entries for "Most Recent Staff Actions" section of the Work Log interface.', 'coust', 'description'),
1580   'interval' ),
1581
1582 ( 'ui.admin.patron_log.max_entries',
1583     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'GUI: Work Log: Maximum Patrons Logged', 'coust', 'label'),
1584     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'Maximum entries for "Most Recently Affected Patrons..." section of the Work Log interface.', 'coust', 'description'),
1585   'interval' ),
1586
1587 ( 'lib.courier_code',
1588     oils_i18n_gettext('lib.courier_code', 'Courier Code', 'coust', 'label'),
1589     oils_i18n_gettext('lib.courier_code', 'Courier Code for the library.  Available in transit slip templates as the %courier_code% macro.', 'coust', 'description'),
1590     'string'),
1591
1592 ( 'circ.block_renews_for_holds',
1593     oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
1594     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'),
1595     'bool' ),
1596
1597 ( 'circ.password_reset_request_per_user_limit',
1598     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
1599     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'),
1600     'string'),
1601
1602 ( 'circ.password_reset_request_time_to_live',
1603     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
1604     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'),
1605     'string'),
1606
1607 ( 'circ.password_reset_request_throttle',
1608     oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
1609     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'),
1610     'string')
1611 ;
1612
1613 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1614         'ui.circ.suppress_checkin_popups',
1615         oils_i18n_gettext(
1616             'ui.circ.suppress_checkin_popups', 
1617             'Circ: Suppress popup-dialogs during check-in.', 
1618             'coust', 
1619             'label'),
1620         oils_i18n_gettext(
1621             'ui.circ.suppress_checkin_popups', 
1622             'Circ: Suppress popup-dialogs during check-in.', 
1623             'coust', 
1624             'description'),
1625         'bool'
1626 );
1627
1628 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1629         'format.date',
1630         oils_i18n_gettext(
1631             'format.date',
1632             'GUI: Format Dates with this pattern.', 
1633             'coust', 
1634             'label'),
1635         oils_i18n_gettext(
1636             'format.date',
1637             'GUI: Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010")', 
1638             'coust', 
1639             'description'),
1640         'string'
1641 ), (
1642         'format.time',
1643         oils_i18n_gettext(
1644             'format.time',
1645             'GUI: Format Times with this pattern.', 
1646             'coust', 
1647             'label'),
1648         oils_i18n_gettext(
1649             'format.time',
1650             '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")', 
1651             'coust', 
1652             'description'),
1653         'string'
1654 );
1655
1656 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1657         'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1658         oils_i18n_gettext(
1659             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1660             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1661             'coust', 
1662             'label'),
1663         oils_i18n_gettext(
1664             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1665             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1666             'coust', 
1667             'description'),
1668         'bool'
1669 );
1670
1671 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1672         'url.remote_column_settings',
1673         oils_i18n_gettext(
1674             'url.remote_column_settings',
1675             'GUI: URL for remote directory containing list column settings.', 
1676             'coust', 
1677             'label'),
1678         oils_i18n_gettext(
1679             'url.remote_column_settings',
1680             '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.', 
1681             'coust', 
1682             'description'),
1683         'string'
1684 );
1685
1686 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1687         'gui.disable_local_save_columns',
1688         oils_i18n_gettext(
1689             'gui.disable_local_save_columns',
1690             'GUI: Disable the ability to save list column configurations locally.', 
1691             'coust', 
1692             'label'),
1693         oils_i18n_gettext(
1694             'gui.disable_local_save_columns',
1695             '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.', 
1696             'coust', 
1697             'description'),
1698         'bool'
1699 );
1700
1701 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1702         'circ.password_reset_request_requires_matching_email',
1703         oils_i18n_gettext(
1704             'circ.password_reset_request_requires_matching_email',
1705             'Circulation: Require matching email address for password reset requests', 
1706             'coust', 
1707             'label'),
1708         oils_i18n_gettext(
1709             'circ.password_reset_request_requires_matching_email',
1710             'Circulation: Require matching email address for password reset requests', 
1711             'coust', 
1712             'description'),
1713         'bool'
1714 );
1715
1716 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1717         'circ.holds.expired_patron_block',
1718         oils_i18n_gettext(
1719             'circ.holds.expired_patron_block',
1720             'Circulation: Block hold request if hold recipient privileges have expired', 
1721             'coust', 
1722             'label'),
1723         oils_i18n_gettext(
1724             'circ.holds.expired_patron_block',
1725             'Circulation: Block hold request if hold recipient privileges have expired', 
1726             'coust', 
1727             'description'),
1728         'bool'
1729 );
1730
1731 INSERT INTO config.org_unit_setting_type
1732     (name, label, description, datatype) VALUES (
1733         'circ.booking_reservation.default_elbow_room',
1734         oils_i18n_gettext(
1735             'circ.booking_reservation.default_elbow_room',
1736             'Booking: Elbow room',
1737             'coust',
1738             'label'
1739         ),
1740         oils_i18n_gettext(
1741             'circ.booking_reservation.default_elbow_room',
1742             '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.',
1743             'coust',
1744             'label'
1745         ),
1746         'interval'
1747     );
1748
1749 -- Org_unit_setting_type(s) that need an fm_class:
1750 INSERT into config.org_unit_setting_type
1751 ( name, label, description, datatype, fm_class ) VALUES
1752 ( 'acq.default_copy_location',
1753   'Default copy location',
1754   null,
1755   'link',
1756   'acpl' );
1757
1758 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1759     'circ.holds.org_unit_target_weight',
1760     'Holds: Org Unit Target Weight',
1761     '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.',
1762     'integer'
1763 );
1764
1765 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1766     'circ.holds.target_holds_by_org_unit_weight',
1767     'Holds: Use weight-based hold targeting',
1768     'Use library weight based hold targeting',
1769     'bool'
1770 );
1771
1772 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1773     'circ.holds.max_org_unit_target_loops',
1774     'Holds: Maximum library target attempts',
1775     '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',
1776     'integer'
1777 );
1778
1779
1780 -- Org setting for overriding the circ lib of a precat copy
1781 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1782     'circ.pre_cat_copy_circ_lib',
1783     'Pre-cat Item Circ Lib',
1784     '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',
1785     'string'
1786 );
1787
1788 -- Circ auto-renew interval setting
1789 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1790     'circ.checkout_auto_renew_age',
1791     'Checkout auto renew age',
1792     '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',
1793     'interval'
1794 );
1795
1796 -- Setting for behind the desk hold pickups
1797 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1798     'circ.holds.behind_desk_pickup_supported',
1799     'Holds: Behind Desk Pickup Supported',
1800     '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',
1801     'bool'
1802 );
1803
1804 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1805         'acq.holds.allow_holds_from_purchase_request',
1806         oils_i18n_gettext(
1807             'acq.holds.allow_holds_from_purchase_request', 
1808             'Allows patrons to create automatic holds from purchase requests.', 
1809             'coust', 
1810             'label'),
1811         oils_i18n_gettext(
1812             'acq.holds.allow_holds_from_purchase_request', 
1813             'Allows patrons to create automatic holds from purchase requests.', 
1814             'coust', 
1815             'description'),
1816         'bool'
1817 );
1818
1819 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1820     'circ.holds.target_skip_me',
1821     'Skip For Hold Targeting',
1822     'When true, don''t target any copies at this org unit for holds',
1823     'bool'
1824 );
1825
1826 -- claims returned mark item missing 
1827 INSERT INTO
1828     config.org_unit_setting_type ( name, label, description, datatype )
1829     VALUES (
1830         'circ.claim_return.mark_missing',
1831         'Claim Return: Mark copy as missing', 
1832         'When a circ is marked as claims-returned, also mark the copy as missing',
1833         'bool'
1834     );
1835
1836 -- claims never checked out mark item missing 
1837 INSERT INTO
1838     config.org_unit_setting_type ( name, label, description, datatype )
1839     VALUES (
1840         'circ.claim_never_checked_out.mark_missing',
1841         'Claim Never Checked Out: Mark copy as missing', 
1842         'When a circ is marked as claims-never-checked-out, mark the copy as missing',
1843         'bool'
1844     );
1845
1846 -- mark damaged void overdue setting
1847 INSERT INTO
1848     config.org_unit_setting_type ( name, label, description, datatype )
1849     VALUES (
1850         'circ.damaged.void_ovedue',
1851         'Mark item damaged voids overdues',
1852         'When an item is marked damaged, overdue fines on the most recent circulation are voided.',
1853         'bool'
1854     );
1855
1856 -- hold cancel display limits
1857 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1858     VALUES (
1859         'circ.holds.canceled.display_count',
1860         'Holds: Canceled holds display count',
1861         'How many canceled holds to show in patron holds interfaces',
1862         'integer'
1863     );
1864
1865 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1866     VALUES (
1867         'circ.holds.canceled.display_age',
1868         'Holds: Canceled holds display age',
1869         'Show all canceled holds that were canceled within this amount of time',
1870         'interval'
1871     );
1872
1873 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1874     VALUES (
1875         'circ.holds.uncancel.reset_request_time',
1876         'Holds: Reset request time on un-cancel',
1877         'When a hold is uncanceled, reset the request time to push it to the end of the queue',
1878         'bool'
1879     );
1880
1881 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1882     VALUES (
1883         'circ.holds.default_shelf_expire_interval',
1884         'Default hold shelf expire interval',
1885         '',
1886         'interval'
1887 );
1888
1889 INSERT INTO config.org_unit_setting_type (name, label, description, datatype, fm_class)
1890     VALUES (
1891         'circ.claim_return.copy_status', 
1892         'Claim Return Copy Status', 
1893         'Claims returned copies are put into this status.  Default is to leave the copy in the Checked Out status',
1894         'link', 
1895         'ccs' 
1896     );
1897
1898 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
1899     VALUES ( 
1900         'circ.max_fine.cap_at_price',
1901         oils_i18n_gettext('circ.max_fine.cap_at_price', 'Circ: Cap Max Fine at Item Price', 'coust', 'label'),
1902         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'),
1903         'bool' 
1904     );
1905
1906 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) 
1907     VALUES ( 
1908         'circ.holds.clear_shelf.copy_status',
1909         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Holds: Clear shelf copy status', 'coust', 'label'),
1910         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'),
1911         'link',
1912         'ccs'
1913     );
1914
1915 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1916     VALUES ( 
1917         'circ.selfcheck.workstation_required',
1918         oils_i18n_gettext('circ.selfcheck.workstation_required', 'Selfcheck: Workstation Required', 'coust', 'label'),
1919         oils_i18n_gettext('circ.selfcheck.workstation_required', 'All selfcheck stations must use a workstation', 'coust', 'description'),
1920         'bool'
1921     ), (
1922         'circ.selfcheck.patron_password_required',
1923         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Selfcheck: Require Patron Password', 'coust', 'label'),
1924         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Patron must log in with barcode and password at selfcheck station', 'coust', 'description'),
1925         'bool'
1926     );
1927
1928 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1929     VALUES ( 
1930         'circ.selfcheck.alert.sound',
1931         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Selfcheck: Audio Alerts', 'coust', 'label'),
1932         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Use audio alerts for selfcheck events', 'coust', 'description'),
1933         'bool'
1934     );
1935
1936 INSERT INTO
1937     config.org_unit_setting_type (name, label, description, datatype)
1938     VALUES (
1939         'notice.telephony.callfile_lines',
1940         'Telephony: Arbitrary line(s) to include in each notice callfile',
1941         $$
1942         This overrides lines from opensrf.xml.
1943         Line(s) must be valid for your target server and platform
1944         (e.g. Asterisk 1.4).
1945         $$,
1946         'string'
1947     );
1948
1949 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1950     VALUES ( 
1951         'circ.offline.username_allowed',
1952         oils_i18n_gettext('circ.offline.username_allowed', 'Offline: Patron Usernames Allowed', 'coust', 'label'),
1953         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'),
1954         'bool'
1955     );
1956
1957 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1958 VALUES (
1959     'acq.fund.balance_limit.warn',
1960     oils_i18n_gettext('acq.fund.balance_limit.warn', 'Fund Spending Limit for Warning', 'coust', 'label'),
1961     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'),
1962     'integer'
1963 );
1964
1965 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1966 VALUES (
1967     'acq.fund.balance_limit.block',
1968     oils_i18n_gettext('acq.fund.balance_limit.block', 'Fund Spending Limit for Block', 'coust', 'label'),
1969     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'),
1970     'integer'
1971 );
1972
1973 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1974     VALUES (
1975         'circ.holds.hold_has_copy_at.alert',
1976         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'Holds: Has Local Copy Alert', 'coust', 'label'),
1977         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'),
1978         'bool'
1979     ),(
1980         'circ.holds.hold_has_copy_at.block',
1981         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'Holds: Has Local Copy Block', 'coust', 'label'),
1982         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'),
1983         'bool'
1984     );
1985
1986 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1987 VALUES (
1988     'auth.persistent_login_interval',
1989     oils_i18n_gettext('auth.persistent_login_interval', 'Persistent Login Duration', 'coust', 'label'),
1990     oils_i18n_gettext('auth.persistent_login_interval', 'How long a persistent login lasts.  E.g. ''2 weeks''', 'coust', 'description'),
1991     'interval'
1992 );
1993
1994 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1995         'cat.marc_control_number_identifier',
1996         oils_i18n_gettext(
1997             'cat.marc_control_number_identifier', 
1998             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
1999             'coust', 
2000             'label'),
2001         oils_i18n_gettext(
2002             'cat.marc_control_number_identifier', 
2003             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2004             'coust', 
2005             'description'),
2006         'string'
2007 );
2008
2009 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) 
2010     VALUES (
2011         'circ.selfcheck.block_checkout_on_copy_status',
2012         oils_i18n_gettext(
2013             'circ.selfcheck.block_checkout_on_copy_status',
2014             'Selfcheck: Block copy checkout status',
2015             'coust',
2016             'label'
2017         ),
2018         oils_i18n_gettext(
2019             'circ.selfcheck.block_checkout_on_copy_status',
2020             'List of copy status IDs that will block checkout even if the generic COPY_NOT_AVAILABLE event is overridden',
2021             'coust',
2022             'description'
2023         ),
2024         'array'
2025     );
2026
2027 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2028 VALUES (
2029     'serial.prev_issuance_copy_location',
2030     oils_i18n_gettext(
2031         'serial.prev_issuance_copy_location',
2032         'Serials: Previous Issuance Copy Location',
2033         'coust',
2034         'label'
2035     ),
2036     oils_i18n_gettext(
2037         'serial.prev_issuance_copy_location',
2038         'When a serial issuance is received, copies (units) of the previous issuance will be automatically moved into the configured shelving location',
2039         'coust',
2040         'descripton'
2041         ),
2042     'link',
2043     'acpl'
2044 );
2045
2046 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2047 VALUES (
2048     'cat.default_classification_scheme',
2049     oils_i18n_gettext(
2050         'cat.default_classification_scheme',
2051         'Cataloging: Default Classification Scheme',
2052         'coust',
2053         'label'
2054     ),
2055     oils_i18n_gettext(
2056         'cat.default_classification_scheme',
2057         'Defines the default classification scheme for new call numbers: 1 = Generic; 2 = Dewey; 3 = LC',
2058         'coust',
2059         'descripton'
2060         ),
2061     'link',
2062     'acnc'
2063 );
2064
2065 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2066         'opac.org_unit_hiding.depth',
2067         oils_i18n_gettext(
2068             'opac.org_unit_hiding.depth',
2069             'OPAC: Org Unit Hiding Depth', 
2070             'coust', 
2071             'label'),
2072         oils_i18n_gettext(
2073             'opac.org_unit_hiding.depth',
2074             '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.', 
2075             'coust', 
2076             'description'),
2077         'integer'
2078 );
2079
2080 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2081     VALUES 
2082         ('circ.holds.alert_if_local_avail',
2083          'Holds: Local available alert',
2084          'If local copy is available, alert the person making the hold',
2085          'bool'),
2086
2087         ('circ.holds.deny_if_local_avail',
2088          'Holds: Local available block',
2089          'If local copy is available, deny the creation of the hold',
2090          'bool')
2091     ;
2092
2093 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2094     VALUES ( 
2095         'circ.holds.clear_shelf.no_capture_holds',
2096         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'Holds: Bypass hold capture during clear shelf process', 'coust', 'label'),
2097         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'During the clear shelf process, avoid capturing new holds on cleared items.', 'coust', 'description'),
2098         'bool'
2099     );
2100
2101 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
2102     'circ.booking_reservation.stop_circ',
2103     'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
2104     '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.',
2105     'bool'
2106 );
2107
2108 ---------------------------------
2109 -- Seed data for usr_setting_type
2110 ----------------------------------
2111
2112 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2113     VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
2114
2115 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2116     VALUES ('opac.default_search_depth', TRUE, 'OPAC Search Depth', 'OPAC Search Depth', 'integer');
2117
2118 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2119     VALUES ('opac.default_search_location', TRUE, 'OPAC Search Location', 'OPAC Search Location', 'integer');
2120
2121 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2122     VALUES ('opac.hits_per_page', TRUE, 'Hits per Page', 'Hits per Page', 'string');
2123
2124 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2125     VALUES ('opac.hold_notify', TRUE, 'Hold Notification Format', 'Hold Notification Format', 'string');
2126
2127 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2128     VALUES ('staff_client.catalog.record_view.default', TRUE, 'Default Record View', 'Default Record View', 'string');
2129
2130 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2131     VALUES ('staff_client.copy_editor.templates', TRUE, 'Copy Editor Template', 'Copy Editor Template', 'object');
2132
2133 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2134     VALUES ('circ.holds_behind_desk', FALSE, 'Hold is behind Circ Desk', 'Hold is behind Circ Desk', 'bool');
2135
2136 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2137     VALUES (
2138         'history.circ.retention_age',
2139         TRUE,
2140         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','label'),
2141         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','description'),
2142         'interval'
2143     ),(
2144         'history.circ.retention_start',
2145         FALSE,
2146         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','label'),
2147         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','description'),
2148         'date'
2149     );
2150
2151 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2152     VALUES (
2153         'history.hold.retention_age',
2154         TRUE,
2155         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','label'),
2156         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','description'),
2157         'interval'
2158     ),(
2159         'history.hold.retention_start',
2160         TRUE,
2161         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','label'),
2162         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','description'),
2163         'interval'
2164     ),(
2165         'history.hold.retention_count',
2166         TRUE,
2167         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','label'),
2168         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','description'),
2169         'integer'
2170     );
2171
2172 INSERT INTO config.usr_setting_type (name, opac_visible, label, description, datatype)
2173     VALUES (
2174         'opac.default_sort',
2175         TRUE,
2176         oils_i18n_gettext(
2177             'opac.default_sort',
2178             'OPAC Default Search Sort',
2179             'cust',
2180             'label'
2181         ),
2182         oils_i18n_gettext(
2183             'opac.default_sort',
2184             'OPAC Default Search Sort',
2185             'cust',
2186             'description'
2187         ),
2188         'string'
2189     );
2190
2191 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2192         'circ.missing_pieces.copy_status',
2193         oils_i18n_gettext(
2194             'circ.missing_pieces.copy_status',
2195             'Circulation: Item Status for Missing Pieces',
2196             'coust',
2197             'label'),
2198         oils_i18n_gettext(
2199             'circ.missing_pieces.copy_status',
2200             '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.',
2201             'coust',
2202             'description'),
2203         'link',
2204         'ccs'
2205 );
2206
2207 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2208         'circ.do_not_tally_claims_returned',
2209         oils_i18n_gettext(
2210             'circ.do_not_tally_claims_returned',
2211             'Circulation: Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.',
2212             'coust',
2213             'label'),
2214         oils_i18n_gettext(
2215             'circ.do_not_tally_claims_returned',
2216             '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.',
2217             'coust',
2218             'description'),
2219         'bool'
2220 );
2221
2222 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2223     VALUES
2224         ('cat.label.font.size',
2225             oils_i18n_gettext('cat.label.font.size',
2226                 'Cataloging: Spine and pocket label font size', 'coust', 'label'),
2227             oils_i18n_gettext('cat.label.font.size',
2228                 'Set the default font size for spine and pocket labels', 'coust', 'description'),
2229             'integer'
2230         )
2231         ,('cat.label.font.family',
2232             oils_i18n_gettext('cat.label.font.family',
2233                 'Cataloging: Spine and pocket label font family', 'coust', 'label'),
2234             oils_i18n_gettext('cat.label.font.family',
2235                 '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".',
2236                 'coust', 'description'),
2237             'string'
2238         )
2239         ,('cat.spine.line.width',
2240             oils_i18n_gettext('cat.spine.line.width',
2241                 'Cataloging: Spine label line width', 'coust', 'label'),
2242             oils_i18n_gettext('cat.spine.line.width',
2243                 'Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.',
2244                 'coust', 'description'),
2245             'integer'
2246         )
2247         ,('cat.spine.line.height',
2248             oils_i18n_gettext('cat.spine.line.height',
2249                 'Cataloging: Spine label maximum lines', 'coust', 'label'),
2250             oils_i18n_gettext('cat.spine.line.height',
2251                 'Set the default maximum number of lines for spine labels.',
2252                 'coust', 'description'),
2253             'integer'
2254         )
2255         ,('cat.spine.line.margin',
2256             oils_i18n_gettext('cat.spine.line.margin',
2257                 'Cataloging: Spine label left margin', 'coust', 'label'),
2258             oils_i18n_gettext('cat.spine.line.margin',
2259                 'Set the left margin for spine labels in number of characters.',
2260                 'coust', 'description'),
2261             'integer'
2262         )
2263 ;
2264
2265 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2266     VALUES
2267         ('cat.label.font.weight',
2268             oils_i18n_gettext('cat.label.font.weight',
2269                 'Cataloging: Spine and pocket label font weight', 'coust', 'label'),
2270             oils_i18n_gettext('cat.label.font.weight',
2271                 'Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".',
2272                 'coust', 'description'),
2273             'string'
2274         )
2275 ;
2276
2277 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2278         'circ.patron_edit.clone.copy_address',
2279         oils_i18n_gettext(
2280             'circ.patron_edit.clone.copy_address',
2281             'Patron Registration: Cloned patrons get address copy',
2282             'coust',
2283             'label'
2284         ),
2285         oils_i18n_gettext(
2286             'circ.patron_edit.clone.copy_address',
2287             'In the Patron editor, copy addresses from the cloned user instead of linking directly to the address',
2288             'coust',
2289             'description'
2290         ),
2291         'bool'
2292 );
2293
2294 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2295         'ui.patron.default_ident_type',
2296         oils_i18n_gettext(
2297             'ui.patron.default_ident_type',
2298             'GUI: Default Ident Type for Patron Registration',
2299             'coust',
2300             'label'),
2301         oils_i18n_gettext(
2302             'ui.patron.default_ident_type',
2303             'This is the default Ident Type for new users in the patron editor.',
2304             'coust',
2305             'description'),
2306         'link',
2307         'cit'
2308 );
2309
2310 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2311         'ui.patron.default_country',
2312         oils_i18n_gettext(
2313             'ui.patron.default_country',
2314             'GUI: Default Country for New Addresses in Patron Editor',
2315             'coust',
2316             'label'),
2317         oils_i18n_gettext(
2318             'ui.patron.default_country',
2319             'This is the default Country for new addresses in the patron editor.',
2320             'coust',
2321             'description'),
2322         'string'
2323 );
2324
2325 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2326         'ui.patron.registration.require_address',
2327         oils_i18n_gettext(
2328             'ui.patron.registration.require_address',
2329             'GUI: Require at least one address for Patron Registration',
2330             'coust',
2331             'label'),
2332         oils_i18n_gettext(
2333             'ui.patron.registration.require_address',
2334             'Enforces a requirement for having at least one address for a patron during registration.',
2335             'coust',
2336             'description'),
2337         'bool'
2338 );
2339
2340 INSERT INTO config.org_unit_setting_type (
2341     name, label, description, datatype
2342 ) VALUES
2343     ('credit.processor.payflowpro.enabled',
2344         'Credit card processing: Enable PayflowPro payments',
2345         'This is NOT the same thing as the settings labeled with just "PayPal."',
2346         'bool'
2347     ),
2348     ('credit.processor.payflowpro.login',
2349         'Credit card processing: PayflowPro login/merchant ID',
2350         'Often the same thing as the PayPal manager login',
2351         'string'
2352     ),
2353     ('credit.processor.payflowpro.password',
2354         'Credit card processing: PayflowPro password',
2355         'PayflowPro password',
2356         'string'
2357     ),
2358     ('credit.processor.payflowpro.testmode',
2359         'Credit card processing: PayflowPro test mode',
2360         'Do not really process transactions, but stay in test mode - uses pilot-payflowpro.paypal.com instead of the usual host',
2361         'bool'
2362     ),
2363     ('credit.processor.payflowpro.vendor',
2364         'Credit card processing: PayflowPro vendor',
2365         'Often the same thing as the login',
2366         'string'
2367     ),
2368     ('credit.processor.payflowpro.partner',
2369         'Credit card processing: PayflowPro partner',
2370         'Often "PayPal" or "VeriSign", sometimes others',
2371         'string'
2372     );
2373
2374 -- Patch the name of an old selfcheck setting
2375 UPDATE actor.org_unit_setting
2376     SET name = 'circ.selfcheck.alert.popup'
2377     WHERE name = 'circ.selfcheck.alert_on_checkout_event';
2378
2379 -- Rename certain existing org_unit settings, if present,
2380 -- and make sure their values are JSON
2381 UPDATE actor.org_unit_setting SET
2382     name = 'circ.holds.default_estimated_wait_interval',
2383     --
2384     -- The value column should be JSON.  The old value should be a number,
2385     -- but it may or may not be quoted.  The following CASE behaves
2386     -- differently depending on whether value is quoted.  It is simplistic,
2387     -- and will be defeated by leading or trailing white space, or various
2388     -- malformations.
2389     --
2390     value = CASE WHEN SUBSTR( value, 1, 1 ) = '"'
2391                 THEN '"' || SUBSTR( value, 2, LENGTH(value) - 2 ) || ' days"'
2392                 ELSE '"' || value || ' days"'
2393             END
2394 WHERE name = 'circ.hold_estimate_wait_interval';
2395
2396 -- Create types for existing org unit settings
2397 -- not otherwise accounted for
2398
2399 INSERT INTO config.org_unit_setting_type(
2400  name,
2401  label,
2402  description
2403 )
2404 SELECT DISTINCT
2405         name,
2406         name,
2407         'FIXME'
2408 FROM
2409         actor.org_unit_setting
2410 WHERE
2411         name NOT IN (
2412                 SELECT name
2413                 FROM config.org_unit_setting_type
2414         );
2415
2416 -- Add foreign key to org_unit_setting
2417
2418 ALTER TABLE actor.org_unit_setting
2419         ADD FOREIGN KEY (name) REFERENCES config.org_unit_setting_type (name)
2420                 DEFERRABLE INITIALLY DEFERRED;
2421
2422 -- Create types for existing user settings
2423 -- not otherwise accounted for
2424
2425 INSERT INTO config.usr_setting_type (
2426         name,
2427         label,
2428         description
2429 )
2430 SELECT DISTINCT
2431         name,
2432         name,
2433         'FIXME'
2434 FROM
2435         actor.usr_setting
2436 WHERE
2437         name NOT IN (
2438                 SELECT name
2439                 FROM config.usr_setting_type
2440         );
2441
2442 -- Add foreign key to user_setting_type
2443
2444 ALTER TABLE actor.usr_setting
2445         ADD FOREIGN KEY (name) REFERENCES config.usr_setting_type (name)
2446                 ON DELETE CASCADE ON UPDATE CASCADE
2447                 DEFERRABLE INITIALLY DEFERRED;
2448
2449 INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES
2450     (1, 'cat.spine.line.margin', 0)
2451     ,(1, 'cat.spine.line.height', 9)
2452     ,(1, 'cat.spine.line.width', 8)
2453     ,(1, 'cat.label.font.family', '"monospace"')
2454     ,(1, 'cat.label.font.size', 10)
2455     ,(1, 'cat.label.font.weight', '"normal"')
2456 ;
2457
2458 ALTER TABLE action_trigger.event_definition ADD COLUMN granularity TEXT;
2459 ALTER TABLE action_trigger.event ADD COLUMN async_output BIGINT REFERENCES action_trigger.event_output (id);
2460 ALTER TABLE action_trigger.event_definition ADD COLUMN usr_field TEXT;
2461 ALTER TABLE action_trigger.event_definition ADD COLUMN opt_in_setting TEXT REFERENCES config.usr_setting_type (name) DEFERRABLE INITIALLY DEFERRED;
2462
2463 CREATE OR REPLACE FUNCTION is_json( TEXT ) RETURNS BOOL AS $f$
2464     use JSON::XS;
2465     my $json = shift();
2466     eval { JSON::XS->new->allow_nonref->decode( $json ) };
2467     return $@ ? 0 : 1;
2468 $f$ LANGUAGE PLPERLU;
2469
2470 ALTER TABLE action_trigger.event ADD COLUMN user_data TEXT CHECK (user_data IS NULL OR is_json( user_data ));
2471
2472 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2473     'hold_request.cancel.expire_no_target',
2474     'ahr',
2475     'A hold is cancelled because no copies were found'
2476 );
2477
2478 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2479     'hold_request.cancel.expire_holds_shelf',
2480     'ahr',
2481     'A hold is cancelled because it was on the holds shelf too long'
2482 );
2483
2484 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2485     'hold_request.cancel.staff',
2486     'ahr',
2487     'A hold is cancelled because it was cancelled by staff'
2488 );
2489
2490 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2491     'hold_request.cancel.patron',
2492     'ahr',
2493     'A hold is cancelled by the patron'
2494 );
2495
2496 -- Fix typos in descriptions
2497 UPDATE action_trigger.hook SET description = 'A hold is successfully placed' WHERE key = 'hold_request.success';
2498 UPDATE action_trigger.hook SET description = 'A hold is attempted but not successfully placed' WHERE key = 'hold_request.failure';
2499
2500 -- Add a hook for renewals
2501 INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('renewal','circ','Item renewed to user');
2502
2503 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');
2504
2505 -- Sample Pre-due Notice --
2506
2507 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template) 
2508     VALUES (6, 'f', 1, '3 Day Courtesy Notice', 'checkout.due', 'CircIsOpen', 'SendEmail', '-3 days', 'due_date', 'usr', 
2509 $$
2510 [%- USE date -%]
2511 [%- user = target.0.usr -%]
2512 To: [%- params.recipient_email || user.email %]
2513 From: [%- params.sender_email || default_sender %]
2514 Subject: Courtesy Notice
2515
2516 Dear [% user.family_name %], [% user.first_given_name %]
2517 As a reminder, the following items are due in 3 days.
2518
2519 [% FOR circ IN target %]
2520     Title: [% circ.target_copy.call_number.record.simple_record.title %] 
2521     Barcode: [% circ.target_copy.barcode %] 
2522     Due: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2523     Item Cost: [% helpers.get_copy_price(circ.target_copy) %]
2524     Library: [% circ.circ_lib.name %]
2525     Library Phone: [% circ.circ_lib.phone %]
2526 [% END %]
2527
2528 $$);
2529
2530 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2531     (6, 'target_copy.call_number.record.simple_record'),
2532     (6, 'usr'),
2533     (6, 'circ_lib.billing_address');
2534
2535 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2536     (6, 'max_delay_age', '"1 day"');
2537
2538 -- also add the max delay age to the default overdue notice event def
2539 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2540     (1, 'max_delay_age', '"1 day"');
2541   
2542 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');
2543
2544 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.');
2545
2546 INSERT INTO action_trigger.hook (
2547         key,
2548         core_type,
2549         description,
2550         passive
2551     ) VALUES (
2552         'hold_request.shelf_expires_soon',
2553         'ahr',
2554         'A hold on the shelf will expire there soon.',
2555         TRUE
2556     );
2557
2558 INSERT INTO action_trigger.event_definition (
2559         id,
2560         active,
2561         owner,
2562         name,
2563         hook,
2564         validator,
2565         reactor,
2566         delay,
2567         delay_field,
2568         group_field,
2569         template
2570     ) VALUES (
2571         7,
2572         FALSE,
2573         1,
2574         'Hold Expires from Shelf Soon',
2575         'hold_request.shelf_expires_soon',
2576         'HoldIsAvailable',
2577         'SendEmail',
2578         '- 1 DAY',
2579         'shelf_expire_time',
2580         'usr',
2581 $$
2582 [%- USE date -%]
2583 [%- user = target.0.usr -%]
2584 To: [%- params.recipient_email || user.email %]
2585 From: [%- params.sender_email || default_sender %]
2586 Subject: Hold Available Notification
2587
2588 Dear [% user.family_name %], [% user.first_given_name %]
2589 You requested holds on the following item(s), which are available for
2590 pickup, but these holds will soon expire.
2591
2592 [% FOR hold IN target %]
2593     [%- data = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
2594     Title: [% data.title %]
2595     Author: [% data.author %]
2596     Library: [% hold.pickup_lib.name %]
2597 [% END %]
2598 $$
2599     );
2600
2601 INSERT INTO action_trigger.environment (
2602         event_def,
2603         path
2604     ) VALUES
2605     ( 7, 'current_copy'),
2606     ( 7, 'pickup_lib.billing_address'),
2607     ( 7, 'usr');
2608
2609 INSERT INTO action_trigger.hook (
2610         key,
2611         core_type,
2612         description,
2613         passive
2614     ) VALUES (
2615         'hold_request.long_wait',
2616         'ahr',
2617         'A patron has been waiting on a hold to be fulfilled for a long time.',
2618         TRUE
2619     );
2620
2621 INSERT INTO action_trigger.event_definition (
2622         id,
2623         active,
2624         owner,
2625         name,
2626         hook,
2627         validator,
2628         reactor,
2629         delay,
2630         delay_field,
2631         group_field,
2632         template
2633     ) VALUES (
2634         9,
2635         FALSE,
2636         1,
2637         'Hold waiting for pickup for long time',
2638         'hold_request.long_wait',
2639         'NOOP_True',
2640         'SendEmail',
2641         '6 MONTHS',
2642         'request_time',
2643         'usr',
2644 $$
2645 [%- USE date -%]
2646 [%- user = target.0.usr -%]
2647 To: [%- params.recipient_email || user.email %]
2648 From: [%- params.sender_email || default_sender %]
2649 Subject: Long Wait Hold Notification
2650
2651 Dear [% user.family_name %], [% user.first_given_name %]
2652
2653 You requested hold(s) on the following item(s), but unfortunately
2654 we have not been able to fulfill your request after a considerable
2655 length of time.  If you would still like to receive these items,
2656 no action is required.
2657
2658 [% FOR hold IN target %]
2659     Title: [% hold.bib_rec.bib_record.simple_record.title %]
2660     Author: [% hold.bib_rec.bib_record.simple_record.author %]
2661 [% END %]
2662 $$
2663 );
2664
2665 INSERT INTO action_trigger.environment (
2666         event_def,
2667         path
2668     ) VALUES
2669     (9, 'pickup_lib'),
2670     (9, 'usr'),
2671     (9, 'bib_rec.bib_record.simple_record');
2672
2673 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2674     VALUES (
2675         'format.selfcheck.checkout',
2676         'circ',
2677         'Formats circ objects for self-checkout receipt',
2678         TRUE
2679     );
2680
2681 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2682     VALUES (
2683         10,
2684         TRUE,
2685         1,
2686         'Self-Checkout Receipt',
2687         'format.selfcheck.checkout',
2688         'NOOP_True',
2689         'ProcessTemplate',
2690         'usr',
2691         'print-on-demand',
2692 $$
2693 [%- USE date -%]
2694 [%- SET user = target.0.usr -%]
2695 [%- SET lib = target.0.circ_lib -%]
2696 [%- SET lib_addr = target.0.circ_lib.billing_address -%]
2697 [%- SET hours = lib.hours_of_operation -%]
2698 <div>
2699     <style> li { padding: 8px; margin 5px; }</style>
2700     <div>[% date.format %]</div>
2701     <div>[% lib.name %]</div>
2702     <div>[% lib_addr.street1 %] [% lib_addr.street2 %]</div>
2703     <div>[% lib_addr.city %], [% lib_addr.state %] [% lb_addr.post_code %]</div>
2704     <div>[% lib.phone %]</div>
2705     <br/>
2706
2707     [% user.family_name %], [% user.first_given_name %]
2708     <ol>
2709     [% FOR circ IN target %]
2710         [%-
2711             SET idx = loop.count - 1;
2712             SET udata =  user_data.$idx
2713         -%]
2714         <li>
2715             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2716             <div>Barcode: [% circ.target_copy.barcode %]</div>
2717             [% IF udata.renewal_failure %]
2718                 <div style='color:red;'>Renewal Failed</div>
2719             [% ELSE %]
2720                 <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2721             [% END %]
2722         </li>
2723     [% END %]
2724     </ol>
2725     
2726     <div>
2727         Library Hours
2728         [%- BLOCK format_time; date.format(time _ ' 1/1/1000', format='%I:%M %p'); END -%]
2729         <div>
2730             Monday 
2731             [% PROCESS format_time time = hours.dow_0_open %] 
2732             [% PROCESS format_time time = hours.dow_0_close %] 
2733         </div>
2734         <div>
2735             Tuesday 
2736             [% PROCESS format_time time = hours.dow_1_open %] 
2737             [% PROCESS format_time time = hours.dow_1_close %] 
2738         </div>
2739         <div>
2740             Wednesday 
2741             [% PROCESS format_time time = hours.dow_2_open %] 
2742             [% PROCESS format_time time = hours.dow_2_close %] 
2743         </div>
2744         <div>
2745             Thursday
2746             [% PROCESS format_time time = hours.dow_3_open %] 
2747             [% PROCESS format_time time = hours.dow_3_close %] 
2748         </div>
2749         <div>
2750             Friday
2751             [% PROCESS format_time time = hours.dow_4_open %] 
2752             [% PROCESS format_time time = hours.dow_4_close %] 
2753         </div>
2754         <div>
2755             Saturday
2756             [% PROCESS format_time time = hours.dow_5_open %] 
2757             [% PROCESS format_time time = hours.dow_5_close %] 
2758         </div>
2759         <div>
2760             Sunday 
2761             [% PROCESS format_time time = hours.dow_6_open %] 
2762             [% PROCESS format_time time = hours.dow_6_close %] 
2763         </div>
2764     </div>
2765 </div>
2766 $$
2767 );
2768
2769 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2770     ( 10, 'target_copy'),
2771     ( 10, 'circ_lib.billing_address'),
2772     ( 10, 'circ_lib.hours_of_operation'),
2773     ( 10, 'usr');
2774
2775 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2776     VALUES (
2777         'format.selfcheck.items_out',
2778         'circ',
2779         'Formats items out for self-checkout receipt',
2780         TRUE
2781     );
2782
2783 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2784     VALUES (
2785         11,
2786         TRUE,
2787         1,
2788         'Self-Checkout Items Out Receipt',
2789         'format.selfcheck.items_out',
2790         'NOOP_True',
2791         'ProcessTemplate',
2792         'usr',
2793         'print-on-demand',
2794 $$
2795 [%- USE date -%]
2796 [%- SET user = target.0.usr -%]
2797 <div>
2798     <style> li { padding: 8px; margin 5px; }</style>
2799     <div>[% date.format %]</div>
2800     <br/>
2801
2802     [% user.family_name %], [% user.first_given_name %]
2803     <ol>
2804     [% FOR circ IN target %]
2805         <li>
2806             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2807             <div>Barcode: [% circ.target_copy.barcode %]</div>
2808             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2809         </li>
2810     [% END %]
2811     </ol>
2812 </div>
2813 $$
2814 );
2815
2816
2817 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2818     ( 11, 'target_copy'),
2819     ( 11, 'circ_lib.billing_address'),
2820     ( 11, 'circ_lib.hours_of_operation'),
2821     ( 11, 'usr');
2822
2823 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2824     VALUES (
2825         'format.selfcheck.holds',
2826         'ahr',
2827         'Formats holds for self-checkout receipt',
2828         TRUE
2829     );
2830
2831 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2832     VALUES (
2833         12,
2834         TRUE,
2835         1,
2836         'Self-Checkout Holds Receipt',
2837         'format.selfcheck.holds',
2838         'NOOP_True',
2839         'ProcessTemplate',
2840         'usr',
2841         'print-on-demand',
2842 $$
2843 [%- USE date -%]
2844 [%- SET user = target.0.usr -%]
2845 <div>
2846     <style> li { padding: 8px; margin 5px; }</style>
2847     <div>[% date.format %]</div>
2848     <br/>
2849
2850     [% user.family_name %], [% user.first_given_name %]
2851     <ol>
2852     [% FOR hold IN target %]
2853         [%-
2854             SET idx = loop.count - 1;
2855             SET udata =  user_data.$idx
2856         -%]
2857         <li>
2858             <div>Title: [% hold.bib_rec.bib_record.simple_record.title %]</div>
2859             <div>Author: [% hold.bib_rec.bib_record.simple_record.author %]</div>
2860             <div>Pickup Location: [% hold.pickup_lib.name %]</div>
2861             <div>Status: 
2862                 [%- IF udata.ready -%]
2863                     Ready for pickup
2864                 [% ELSE %]
2865                     #[% udata.queue_position %] of [% udata.potential_copies %] copies.
2866                 [% END %]
2867             </div>
2868         </li>
2869     [% END %]
2870     </ol>
2871 </div>
2872 $$
2873 );
2874
2875
2876 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2877     ( 12, 'bib_rec.bib_record.simple_record'),
2878     ( 12, 'pickup_lib'),
2879     ( 12, 'usr');
2880
2881 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2882     VALUES (
2883         'format.selfcheck.fines',
2884         'au',
2885         'Formats fines for self-checkout receipt',
2886         TRUE
2887     );
2888
2889 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template )
2890     VALUES (
2891         13,
2892         TRUE,
2893         1,
2894         'Self-Checkout Fines Receipt',
2895         'format.selfcheck.fines',
2896         'NOOP_True',
2897         'ProcessTemplate',
2898         'print-on-demand',
2899 $$
2900 [%- USE date -%]
2901 [%- SET user = target -%]
2902 <div>
2903     <style> li { padding: 8px; margin 5px; }</style>
2904     <div>[% date.format %]</div>
2905     <br/>
2906
2907     [% user.family_name %], [% user.first_given_name %]
2908     <ol>
2909     [% FOR xact IN user.open_billable_transactions_summary %]
2910         <li>
2911             <div>Details: 
2912                 [% IF xact.xact_type == 'circulation' %]
2913                     [%- helpers.get_copy_bib_basics(xact.circulation.target_copy).title -%]
2914                 [% ELSE %]
2915                     [%- xact.last_billing_type -%]
2916                 [% END %]
2917             </div>
2918             <div>Total Billed: [% xact.total_owed %]</div>
2919             <div>Total Paid: [% xact.total_paid %]</div>
2920             <div>Balance Owed : [% xact.balance_owed %]</div>
2921         </li>
2922     [% END %]
2923     </ol>
2924 </div>
2925 $$
2926 );
2927
2928 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2929     ( 13, 'open_billable_transactions_summary.circulation' );
2930
2931 INSERT INTO action_trigger.reactor (module,description) VALUES
2932 (   'SendFile',
2933     oils_i18n_gettext(
2934         'SendFile',
2935         '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.',
2936         'atreact',
2937         'description'
2938     )
2939 );
2940
2941 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2942     VALUES (
2943         'format.acqli.html',
2944         'jub',
2945         'Formats lineitem worksheet for titles received',
2946         TRUE
2947     );
2948
2949 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template)
2950     VALUES (
2951         14,
2952         TRUE,
2953         1,
2954         'Lineitem Worksheet',
2955         'format.acqli.html',
2956         'NOOP_True',
2957         'ProcessTemplate',
2958         'print-on-demand',
2959 $$
2960 [%- USE date -%]
2961 [%- SET li = target; -%]
2962 <div class="wrapper">
2963     <div class="summary" style='font-size:110%; font-weight:bold;'>
2964
2965         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
2966         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
2967         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
2968         <div class="lineid">Lineitem ID: [% li.id %]</div>
2969
2970         [% IF li.distribution_formulas.size > 0 %]
2971             [% SET forms = [] %]
2972             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
2973             <div>Distribution Formulas: [% forms.join(',') %]</div>
2974         [% END %]
2975
2976         [% IF li.lineitem_notes.size > 0 %]
2977             Lineitem Notes:
2978             <ul>
2979                 [%- FOR note IN li.lineitem_notes -%]
2980                     <li>
2981                     [% IF note.alert_text %]
2982                         [% note.alert_text.code -%] 
2983                         [% IF note.value -%]
2984                             : [% note.value %]
2985                         [% END %]
2986                     [% ELSE %]
2987                         [% note.value -%] 
2988                     [% END %]
2989                     </li>
2990                 [% END %]
2991             </ul>
2992         [% END %]
2993     </div>
2994     <br/>
2995     <table>
2996         <thead>
2997             <tr>
2998                 <th>Branch</th>
2999                 <th>Barcode</th>
3000                 <th>Call Number</th>
3001                 <th>Fund</th>
3002                 <th>Recd.</th>
3003                 <th>Notes</th>
3004             </tr>
3005         </thead>
3006         <tbody>
3007         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
3008             [% 
3009                 IF copy.eg_copy_id;
3010                     SET copy = copy.eg_copy_id;
3011                     SET cn_label = copy.call_number.label;
3012                 ELSE; 
3013                     SET copy = detail; 
3014                     SET cn_label = detail.cn_label;
3015                 END 
3016             %]
3017             <tr>
3018                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
3019                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
3020                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
3021                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
3022                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
3023                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
3024                 <td style='padding:5px;'>[% detail.note %]</td>
3025             </tr>
3026         [% END %]
3027         </tbody>
3028     </table>
3029 </div>
3030 $$
3031 );
3032
3033
3034 INSERT INTO action_trigger.environment (event_def, path) VALUES
3035     ( 14, 'attributes' ),
3036     ( 14, 'lineitem_details' ),
3037     ( 14, 'lineitem_details.owning_lib' ),
3038     ( 14, 'lineitem_notes' )
3039 ;
3040
3041 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3042         'aur.ordered',
3043         'aur', 
3044         oils_i18n_gettext(
3045             'aur.ordered',
3046             'A patron acquisition request has been marked On-Order.',
3047             'ath',
3048             'description'
3049         ), 
3050         TRUE
3051     ), (
3052         'aur.received', 
3053         'aur', 
3054         oils_i18n_gettext(
3055             'aur.received', 
3056             'A patron acquisition request has been marked Received.',
3057             'ath',
3058             'description'
3059         ),
3060         TRUE
3061     ), (
3062         'aur.cancelled',
3063         'aur',
3064         oils_i18n_gettext(
3065             'aur.cancelled',
3066             'A patron acquisition request has been marked Cancelled.',
3067             'ath',
3068             'description'
3069         ),
3070         TRUE
3071     )
3072 ;
3073
3074 INSERT INTO action_trigger.validator (module,description) VALUES (
3075         'Acq::UserRequestOrdered',
3076         oils_i18n_gettext(
3077             'Acq::UserRequestOrdered',
3078             'Tests to see if the corresponding Line Item has a state of "on-order".',
3079             'atval',
3080             'description'
3081         )
3082     ), (
3083         'Acq::UserRequestReceived',
3084         oils_i18n_gettext(
3085             'Acq::UserRequestReceived',
3086             'Tests to see if the corresponding Line Item has a state of "received".',
3087             'atval',
3088             'description'
3089         )
3090     ), (
3091         'Acq::UserRequestCancelled',
3092         oils_i18n_gettext(
3093             'Acq::UserRequestCancelled',
3094             'Tests to see if the corresponding Line Item has a state of "cancelled".',
3095             'atval',
3096             'description'
3097         )
3098     )
3099 ;
3100
3101 -- What was event_definition #15 in v1.6.1 will be recreated as #20.  This
3102 -- renumbering requires some juggling:
3103 --
3104 -- 1. Update any child rows to point to #20.  These updates will temporarily
3105 -- violate foreign key constraints, but that's okay as long as we create
3106 -- #20 before committing.
3107 --
3108 -- 2. Delete the old #15.
3109 --
3110 -- 3. Insert the new #15.
3111 --
3112 -- 4. Insert #20.
3113 --
3114 -- We could combine steps 2 and 3 into a single update, but that would create
3115 -- additional opportunities for typos, since we already have the insert from
3116 -- an upgrade script.
3117
3118 UPDATE action_trigger.environment
3119 SET event_def = 20
3120 WHERE event_def = 15;
3121
3122 UPDATE action_trigger.event
3123 SET event_def = 20
3124 WHERE event_def = 15;
3125
3126 UPDATE action_trigger.event_params
3127 SET event_def = 20
3128 WHERE event_def = 15;
3129
3130 DELETE FROM action_trigger.event_definition
3131 WHERE id = 15;
3132
3133 INSERT INTO action_trigger.event_definition (
3134         id,
3135         active,
3136         owner,
3137         name,
3138         hook,
3139         validator,
3140         reactor,
3141         template
3142     ) VALUES (
3143         15,
3144         FALSE,
3145         1,
3146         'Email Notice: Patron Acquisition Request marked On-Order.',
3147         'aur.ordered',
3148         'Acq::UserRequestOrdered',
3149         'SendEmail',
3150 $$
3151 [%- USE date -%]
3152 [%- SET li = target.lineitem; -%]
3153 [%- SET user = target.usr -%]
3154 [%- SET title = helpers.get_li_attr("title", "", li.attributes) -%]
3155 [%- SET author = helpers.get_li_attr("author", "", li.attributes) -%]
3156 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) -%]
3157 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) -%]
3158 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3159 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3160
3161 To: [%- params.recipient_email || user.email %]
3162 From: [%- params.sender_email || default_sender %]
3163 Subject: Acquisition Request Notification
3164
3165 Dear [% user.family_name %], [% user.first_given_name %]
3166 Our records indicate the following acquisition request has been placed on order.
3167
3168 Title: [% title %]
3169 [% IF author %]Author: [% author %][% END %]
3170 [% IF edition %]Edition: [% edition %][% END %]
3171 [% IF isbn %]ISBN: [% isbn %][% END %]
3172 [% IF publisher %]Publisher: [% publisher %][% END %]
3173 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3174 Lineitem ID: [% li.id %]
3175 $$
3176     ), (
3177         16,
3178         FALSE,
3179         1,
3180         'Email Notice: Patron Acquisition Request marked Received.',
3181         'aur.received',
3182         'Acq::UserRequestReceived',
3183         'SendEmail',
3184 $$
3185 [%- USE date -%]
3186 [%- SET li = target.lineitem; -%]
3187 [%- SET user = target.usr -%]
3188 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3189 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3190 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3191 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3192 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3193 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3194
3195 To: [%- params.recipient_email || user.email %]
3196 From: [%- params.sender_email || default_sender %]
3197 Subject: Acquisition Request Notification
3198
3199 Dear [% user.family_name %], [% user.first_given_name %]
3200 Our records indicate the materials for the following acquisition request have been received.
3201
3202 Title: [% title %]
3203 [% IF author %]Author: [% author %][% END %]
3204 [% IF edition %]Edition: [% edition %][% END %]
3205 [% IF isbn %]ISBN: [% isbn %][% END %]
3206 [% IF publisher %]Publisher: [% publisher %][% END %]
3207 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3208 Lineitem ID: [% li.id %]
3209 $$
3210     ), (
3211         17,
3212         FALSE,
3213         1,
3214         'Email Notice: Patron Acquisition Request marked Cancelled.',
3215         'aur.cancelled',
3216         'Acq::UserRequestCancelled',
3217         'SendEmail',
3218 $$
3219 [%- USE date -%]
3220 [%- SET li = target.lineitem; -%]
3221 [%- SET user = target.usr -%]
3222 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3223 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3224 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3225 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3226 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3227 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3228
3229 To: [%- params.recipient_email || user.email %]
3230 From: [%- params.sender_email || default_sender %]
3231 Subject: Acquisition Request Notification
3232
3233 Dear [% user.family_name %], [% user.first_given_name %]
3234 Our records indicate the following acquisition request has been cancelled.
3235
3236 Title: [% title %]
3237 [% IF author %]Author: [% author %][% END %]
3238 [% IF edition %]Edition: [% edition %][% END %]
3239 [% IF isbn %]ISBN: [% isbn %][% END %]
3240 [% IF publisher %]Publisher: [% publisher %][% END %]
3241 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3242 Lineitem ID: [% li.id %]
3243 $$
3244     );
3245
3246 INSERT INTO action_trigger.environment (
3247         event_def,
3248         path
3249     ) VALUES 
3250         ( 15, 'lineitem' ),
3251         ( 15, 'lineitem.attributes' ),
3252         ( 15, 'usr' ),
3253
3254         ( 16, 'lineitem' ),
3255         ( 16, 'lineitem.attributes' ),
3256         ( 16, 'usr' ),
3257
3258         ( 17, 'lineitem' ),
3259         ( 17, 'lineitem.attributes' ),
3260         ( 17, 'usr' )
3261     ;
3262
3263 INSERT INTO action_trigger.event_definition
3264 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
3265 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
3266 $$
3267 [%- USE date -%]
3268 [%# start JEDI document 
3269   # Vendor specific kludges:
3270   # BT      - vendcode goes to NAD/BY *suffix*  w/ 91 qualifier
3271   # INGRAM  - vendcode goes to NAD/BY *segment* w/ 91 qualifier (separately)
3272   # BRODART - vendcode goes to FTX segment (lineitem level)
3273 -%]
3274 [%- 
3275 IF target.provider.edi_default.vendcode && target.provider.code == 'BRODART';
3276     xtra_ftx = target.provider.edi_default.vendcode;
3277 END;
3278 -%]
3279 [%- BLOCK big_block -%]
3280 {
3281    "recipient":"[% target.provider.san %]",
3282    "sender":"[% target.ordering_agency.mailing_address.san %]",
3283    "body": [{
3284      "ORDERS":[ "order", {
3285         "po_number":[% target.id %],
3286         "date":"[% date.format(date.now, '%Y%m%d') %]",
3287         "buyer":[
3288             [%   IF   target.provider.edi_default.vendcode && (target.provider.code == 'BT' || target.provider.name.match('(?i)^BAKER & TAYLOR'))  -%]
3289                 {"id-qualifier": 91, "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]"}
3290             [%- ELSIF target.provider.edi_default.vendcode && target.provider.code == 'INGRAM' -%]
3291                 {"id":"[% target.ordering_agency.mailing_address.san %]"},
3292                 {"id-qualifier": 91, "id":"[% target.provider.edi_default.vendcode %]"}
3293             [%- ELSE -%]
3294                 {"id":"[% target.ordering_agency.mailing_address.san %]"}
3295             [%- END -%]
3296         ],
3297         "vendor":[
3298             [%- # target.provider.name (target.provider.id) -%]
3299             "[% target.provider.san %]",
3300             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
3301         ],
3302         "currency":"[% target.provider.currency_type %]",
3303                 
3304         "items":[
3305         [%- FOR li IN target.lineitems %]
3306         {
3307             "line_index":"[% li.id %]",
3308             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
3309             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
3310                 [% IF isbn.length == 13 -%]
3311                 {"id-qualifier":"EN","id":"[% isbn %]"},
3312                 [% ELSE -%]
3313                 {"id-qualifier":"IB","id":"[% isbn %]"},
3314                 [%- END %]
3315             [% END %]
3316                 {"id-qualifier":"IN","id":"[% li.id %]"}
3317             ],
3318             "price":[% li.estimated_unit_price || '0.00' %],
3319             "desc":[
3320                 {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"},
3321                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
3322                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
3323                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
3324             ],
3325             [%- ftx_vals = []; 
3326                 FOR note IN li.lineitem_notes; 
3327                     NEXT UNLESS note.vendor_public == 't'; 
3328                     ftx_vals.push(note.value); 
3329                 END; 
3330                 IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
3331                 IF ftx_vals.size == 0; ftx_vals.unshift('');       END;  # BT needs FTX+LIN for every LI, even if it is an empty one
3332             -%]
3333
3334             "free-text":[ 
3335                 [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
3336             ],            
3337             "quantity":[% li.lineitem_details.size %]
3338         }[% UNLESS loop.last %],[% END %]
3339         [%-# TODO: lineitem details (later) -%]
3340         [% END %]
3341         ],
3342         "line_items":[% target.lineitems.size %]
3343      }]  [%# close ORDERS array %]
3344    }]    [%# close  body  array %]
3345 }
3346 [% END %]
3347 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
3348
3349 $$);
3350
3351 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3352   (23, 'lineitems.attributes'), 
3353   (23, 'lineitems.lineitem_details'), 
3354   (23, 'lineitems.lineitem_notes'), 
3355   (23, 'ordering_agency.mailing_address'), 
3356   (23, 'provider');
3357
3358 UPDATE action_trigger.event_definition SET template = 
3359 $$
3360 [%- USE date -%]
3361 [%-
3362     # find a lineitem attribute by name and optional type
3363     BLOCK get_li_attr;
3364         FOR attr IN li.attributes;
3365             IF attr.attr_name == attr_name;
3366                 IF !attr_type OR attr_type == attr.attr_type;
3367                     attr.attr_value;
3368                     LAST;
3369                 END;
3370             END;
3371         END;
3372     END
3373 -%]
3374
3375 <h2>Purchase Order [% target.id %]</h2>
3376 <br/>
3377 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
3378 <br/>
3379
3380 <style>
3381     table td { padding:5px; border:1px solid #aaa;}
3382     table { width:95%; border-collapse:collapse; }
3383     #vendor-notes { padding:5px; border:1px solid #aaa; }
3384 </style>
3385 <table id='vendor-table'>
3386   <tr>
3387     <td valign='top'>Vendor</td>
3388     <td>
3389       <div>[% target.provider.name %]</div>
3390       <div>[% target.provider.addresses.0.street1 %]</div>
3391       <div>[% target.provider.addresses.0.street2 %]</div>
3392       <div>[% target.provider.addresses.0.city %]</div>
3393       <div>[% target.provider.addresses.0.state %]</div>
3394       <div>[% target.provider.addresses.0.country %]</div>
3395       <div>[% target.provider.addresses.0.post_code %]</div>
3396     </td>
3397     <td valign='top'>Ship to / Bill to</td>
3398     <td>
3399       <div>[% target.ordering_agency.name %]</div>
3400       <div>[% target.ordering_agency.billing_address.street1 %]</div>
3401       <div>[% target.ordering_agency.billing_address.street2 %]</div>
3402       <div>[% target.ordering_agency.billing_address.city %]</div>
3403       <div>[% target.ordering_agency.billing_address.state %]</div>
3404       <div>[% target.ordering_agency.billing_address.country %]</div>
3405       <div>[% target.ordering_agency.billing_address.post_code %]</div>
3406     </td>
3407   </tr>
3408 </table>
3409
3410 <br/><br/>
3411 <fieldset id='vendor-notes'>
3412     <legend>Notes to the Vendor</legend>
3413     <ul>
3414     [% FOR note IN target.notes %]
3415         [% IF note.vendor_public == 't' %]
3416             <li>[% note.value %]</li>
3417         [% END %]
3418     [% END %]
3419     </ul>
3420 </fieldset>
3421 <br/><br/>
3422
3423 <table>
3424   <thead>
3425     <tr>
3426       <th>PO#</th>
3427       <th>ISBN or Item #</th>
3428       <th>Title</th>
3429       <th>Quantity</th>
3430       <th>Unit Price</th>
3431       <th>Line Total</th>
3432       <th>Notes</th>
3433     </tr>
3434   </thead>
3435   <tbody>
3436
3437   [% subtotal = 0 %]
3438   [% FOR li IN target.lineitems %]
3439
3440   <tr>
3441     [% count = li.lineitem_details.size %]
3442     [% price = li.estimated_unit_price %]
3443     [% litotal = (price * count) %]
3444     [% subtotal = subtotal + litotal %]
3445     [% isbn = PROCESS get_li_attr attr_name = 'isbn' %]
3446     [% ident = PROCESS get_li_attr attr_name = 'identifier' %]
3447
3448     <td>[% target.id %]</td>
3449     <td>[% isbn || ident %]</td>
3450     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
3451     <td>[% count %]</td>
3452     <td>[% price %]</td>
3453     <td>[% litotal %]</td>
3454     <td>
3455         <ul>
3456         [% FOR note IN li.lineitem_notes %]
3457             [% IF note.vendor_public == 't' %]
3458                 <li>[% note.value %]</li>
3459             [% END %]
3460         [% END %]
3461         </ul>
3462     </td>
3463   </tr>
3464   [% END %]
3465   <tr>
3466     <td/><td/><td/><td/>
3467     <td>Subtotal</td>
3468     <td>[% subtotal %]</td>
3469   </tr>
3470   </tbody>
3471 </table>
3472
3473 <br/>
3474
3475 Total Line Item Count: [% target.lineitems.size %]
3476 $$
3477 WHERE id = 4;
3478
3479 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3480     (4, 'lineitems.lineitem_notes'),
3481     (4, 'notes');
3482
3483 INSERT INTO action_trigger.environment (event_def, path) VALUES
3484     ( 14, 'lineitem_notes.alert_text' ),
3485     ( 14, 'distribution_formulas.formula' ),
3486     ( 14, 'lineitem_details.fund' ),
3487     ( 14, 'lineitem_details.eg_copy_id' ),
3488     ( 14, 'lineitem_details.eg_copy_id.call_number' )
3489 ;
3490
3491 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3492         'aur.created',
3493         'aur',
3494         oils_i18n_gettext(
3495             'aur.created',
3496             'A patron has made an acquisitions request.',
3497             'ath',
3498             'description'
3499         ),
3500         TRUE
3501     ), (
3502         'aur.rejected',
3503         'aur',
3504         oils_i18n_gettext(
3505             'aur.rejected',
3506             'A patron acquisition request has been rejected.',
3507             'ath',
3508             'description'
3509         ),
3510         TRUE
3511     )
3512 ;
3513
3514 INSERT INTO action_trigger.event_definition (
3515         id,
3516         active,
3517         owner,
3518         name,
3519         hook,
3520         validator,
3521         reactor,
3522         template
3523     ) VALUES (
3524         18,
3525         FALSE,
3526         1,
3527         'Email Notice: Acquisition Request created.',
3528         'aur.created',
3529         'NOOP_True',
3530         'SendEmail',
3531 $$
3532 [%- USE date -%]
3533 [%- SET user = target.usr -%]
3534 [%- SET title = target.title -%]
3535 [%- SET author = target.author -%]
3536 [%- SET isxn = target.isxn -%]
3537 [%- SET publisher = target.publisher -%]
3538 [%- SET pubdate = target.pubdate -%]
3539
3540 To: [%- params.recipient_email || user.email %]
3541 From: [%- params.sender_email || default_sender %]
3542 Subject: Acquisition Request Notification
3543
3544 Dear [% user.family_name %], [% user.first_given_name %]
3545 Our records indicate that you have made the following acquisition request:
3546
3547 Title: [% title %]
3548 [% IF author %]Author: [% author %][% END %]
3549 [% IF edition %]Edition: [% edition %][% END %]
3550 [% IF isbn %]ISXN: [% isxn %][% END %]
3551 [% IF publisher %]Publisher: [% publisher %][% END %]
3552 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3553 $$
3554     ), (
3555         19,
3556         FALSE,
3557         1,
3558         'Email Notice: Acquisition Request Rejected.',
3559         'aur.rejected',
3560         'NOOP_True',
3561         'SendEmail',
3562 $$
3563 [%- USE date -%]
3564 [%- SET user = target.usr -%]
3565 [%- SET title = target.title -%]
3566 [%- SET author = target.author -%]
3567 [%- SET isxn = target.isxn -%]
3568 [%- SET publisher = target.publisher -%]
3569 [%- SET pubdate = target.pubdate -%]
3570 [%- SET cancel_reason = target.cancel_reason.description -%]
3571
3572 To: [%- params.recipient_email || user.email %]
3573 From: [%- params.sender_email || default_sender %]
3574 Subject: Acquisition Request Notification
3575
3576 Dear [% user.family_name %], [% user.first_given_name %]
3577 Our records indicate the following acquisition request has been rejected for this reason: [% cancel_reason %]
3578
3579 Title: [% title %]
3580 [% IF author %]Author: [% author %][% END %]
3581 [% IF edition %]Edition: [% edition %][% END %]
3582 [% IF isbn %]ISBN: [% isbn %][% END %]
3583 [% IF publisher %]Publisher: [% publisher %][% END %]
3584 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3585 $$
3586     );
3587
3588 INSERT INTO action_trigger.environment (
3589         event_def,
3590         path
3591     ) VALUES 
3592         ( 18, 'usr' ),
3593         ( 19, 'usr' ),
3594         ( 19, 'cancel_reason' )
3595     ;
3596
3597 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, template) 
3598     VALUES (20, 'f', 1, 'Password reset request notification', 'password.reset_request', 'NOOP_True', 'SendEmail', '00:00:01',
3599 $$
3600 [%- USE date -%]
3601 [%- user = target.usr -%]
3602 To: [%- params.recipient_email || user.email %]
3603 From: [%- params.sender_email || user.home_ou.email || default_sender %]
3604 Subject: [% user.home_ou.name %]: library account password reset request
3605   
3606 You have received this message because you, or somebody else, requested a reset
3607 of your library system password. If you did not request a reset of your library
3608 system password, just ignore this message and your current password will
3609 continue to work.
3610
3611 If you did request a reset of your library system password, please perform
3612 the following steps to continue the process of resetting your password:
3613
3614 1. Open the following link in a web browser: https://[% params.hostname %]/opac/password/[% params.locale || 'en-US' %]/[% target.uuid %]
3615 The browser displays a password reset form.
3616
3617 2. Enter your new password in the password reset form in the browser. You must
3618 enter the password twice to ensure that you do not make a mistake. If the
3619 passwords match, you will then be able to log in to your library system account
3620 with the new password.
3621
3622 $$);
3623
3624 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3625     VALUES (
3626         'format.acqcle.html',
3627         'acqcle',
3628         'Formats claim events into a voucher',
3629         TRUE
3630     );
3631
3632 INSERT INTO action_trigger.event_definition (
3633         id, active, owner, name, hook, group_field,
3634         validator, reactor, granularity, template
3635     ) VALUES (
3636         21,
3637         TRUE,
3638         1,
3639         'Claim Voucher',
3640         'format.acqcle.html',
3641         'claim',
3642         'NOOP_True',
3643         'ProcessTemplate',
3644         'print-on-demand',
3645 $$
3646 [%- USE date -%]
3647 [%- SET claim = target.0.claim -%]
3648 <!-- This will need refined/prettified. -->
3649 <div class="acq-claim-voucher">
3650     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
3651     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
3652     <ul>
3653         [% FOR event IN target %]
3654         <li>
3655             Event type: [% event.type.code %]
3656             [% IF event.type.library_initiated %](Library initiated)[% END %]
3657             <br />
3658             Event date: [% event.event_date %]<br />
3659             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
3660             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
3661             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
3662             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
3663             [% event.claim.lineitem_detail.fund.code %]
3664             ([% event.claim.lineitem_detail.fund.year %])
3665         </li>
3666         [% END %]
3667     </ul>
3668 </div>
3669 $$
3670 );
3671
3672 INSERT INTO action_trigger.environment (event_def, path) VALUES
3673     (21, 'claim'),
3674     (21, 'claim.type'),
3675     (21, 'claim.lineitem_detail'),
3676     (21, 'claim.lineitem_detail.fund'),
3677     (21, 'claim.lineitem_detail.lineitem.attributes'),
3678     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
3679     (21, 'creator'),
3680     (21, 'type')
3681 ;
3682
3683 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3684     VALUES (
3685         'format.acqinv.html',
3686         'acqinv',
3687         'Formats invoices into a voucher',
3688         TRUE
3689     );
3690
3691 INSERT INTO action_trigger.event_definition (
3692         id, active, owner, name, hook,
3693         validator, reactor, granularity, template
3694     ) VALUES (
3695         22,
3696         TRUE,
3697         1,
3698         'Invoice',
3699         'format.acqinv.html',
3700         'NOOP_True',
3701         'ProcessTemplate',
3702         'print-on-demand',
3703 $$
3704 [% FILTER collapse %]
3705 [%- SET invoice = target -%]
3706 <!-- This lacks totals, info about funds (for invoice entries,
3707     funds are per-LID!), and general refinement -->
3708 <div class="acq-invoice-voucher">
3709     <h1>Invoice</h1>
3710     <div>
3711         <strong>No.</strong> [% invoice.inv_ident %]
3712         [% IF invoice.inv_type %]
3713             / <strong>Type:</strong>[% invoice.inv_type %]
3714         [% END %]
3715     </div>
3716     <div>
3717         <dl>
3718             [% BLOCK ent_with_address %]
3719             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3720             <dd>
3721                 [% IF ent.addresses.0 %]
3722                     [% SET addr = ent.addresses.0 %]
3723                     [% addr.street1 %]<br />
3724                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3725                     [% addr.city %],
3726                     [% IF addr.county %] [% addr.county %], [% END %]
3727                     [% IF addr.state %] [% addr.state %] [% END %]
3728                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3729                     [% IF addr.country %] [% addr.country %] [% END %]
3730                 [% END %]
3731                 <p>
3732                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3733                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3734                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3735                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3736                 </p>
3737             </dd>
3738             [% END %]
3739             [% INCLUDE ent_with_address
3740                 ent = invoice.provider
3741                 ent_label = "Provider" %]
3742             [% INCLUDE ent_with_address
3743                 ent = invoice.shipper
3744                 ent_label = "Shipper" %]
3745             <dt>Receiver</dt>
3746             <dd>
3747                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3748             </dd>
3749             <dt>Received</dt>
3750             <dd>
3751                 [% helpers.format_date(invoice.recv_date) %] by
3752                 [% invoice.recv_method %]
3753             </dd>
3754             [% IF invoice.note %]
3755                 <dt>Note</dt>
3756                 <dd>
3757                     [% invoice.note %]
3758                 </dd>
3759             [% END %]
3760         </dl>
3761     </div>
3762     <ul>
3763         [% FOR entry IN invoice.entries %]
3764             <li>
3765                 [% IF entry.lineitem %]
3766                     Title: [% helpers.get_li_attr(
3767                         "title", "", entry.lineitem.attributes
3768                     ) %]<br />
3769                     Author: [% helpers.get_li_attr(
3770                         "author", "", entry.lineitem.attributes
3771                     ) %]
3772                 [% END %]
3773                 [% IF entry.purchase_order %]
3774                     (PO: [% entry.purchase_order.name %])
3775                 [% END %]<br />
3776                 Invoice item count: [% entry.inv_item_count %]
3777                 [% IF entry.phys_item_count %]
3778                     / Physical item count: [% entry.phys_item_count %]
3779                 [% END %]
3780                 <br />
3781                 [% IF entry.cost_billed %]
3782                     Cost billed: [% entry.cost_billed %]
3783                     [% IF entry.billed_per_item %](per item)[% END %]
3784                     <br />
3785                 [% END %]
3786                 [% IF entry.actual_cost %]
3787                     Actual cost: [% entry.actual_cost %]<br />
3788                 [% END %]
3789                 [% IF entry.amount_paid %]
3790                     Amount paid: [% entry.amount_paid %]<br />
3791                 [% END %]
3792                 [% IF entry.note %]Note: [% entry.note %][% END %]
3793             </li>
3794         [% END %]
3795         [% FOR item IN invoice.items %]
3796             <li>
3797                 [% IF item.inv_item_type %]
3798                     Item Type: [% item.inv_item_type %]<br />
3799                 [% END %]
3800                 [% IF item.title %]Title/Description:
3801                     [% item.title %]<br />
3802                 [% END %]
3803                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3804                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3805                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3806                 [% IF item.cost_billed %]
3807                     Cost billed: [% item.cost_billed %]<br />
3808                 [% END %]
3809                 [% IF item.actual_cost %]
3810                     Actual cost: [% item.actual_cost %]<br />
3811                 [% END %]
3812                 [% IF item.amount_paid %]
3813                     Amount paid: [% item.amount_paid %]<br />
3814                 [% END %]
3815             </li>
3816         [% END %]
3817     </ul>
3818 </div>
3819 [% END %]
3820 $$
3821 );
3822
3823 INSERT INTO action_trigger.environment (event_def, path) VALUES
3824     (22, 'provider'),
3825     (22, 'provider.addresses'),
3826     (22, 'shipper'),
3827     (22, 'shipper.addresses'),
3828     (22, 'receiver'),
3829     (22, 'entries'),
3830     (22, 'entries.purchase_order'),
3831     (22, 'entries.lineitem'),
3832     (22, 'entries.lineitem.attributes'),
3833     (22, 'items')
3834 ;
3835
3836 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3837   (23, 'provider.edi_default');
3838
3839 INSERT INTO action_trigger.validator (module, description) 
3840     VALUES (
3841         'Acq::PurchaseOrderEDIRequired',
3842         oils_i18n_gettext(
3843             'Acq::PurchaseOrderEDIRequired',
3844             'Purchase order is delivered via EDI',
3845             'atval',
3846             'description'
3847         )
3848     );
3849
3850 INSERT INTO action_trigger.reactor (module, description)
3851     VALUES (
3852         'GeneratePurchaseOrderJEDI',
3853         oils_i18n_gettext(
3854             'GeneratePurchaseOrderJEDI',
3855             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
3856             'atreact',
3857             'description'
3858         )
3859     );
3860
3861 UPDATE action_trigger.hook 
3862     SET 
3863         key = 'acqpo.activated', 
3864         passive = FALSE,
3865         description = oils_i18n_gettext(
3866             'acqpo.activated',
3867             'Purchase order was activated',
3868             'ath',
3869             'description'
3870         )
3871     WHERE key = 'format.po.jedi';
3872
3873 -- We just changed a key in action_trigger.hook.  Now redirect any
3874 -- child rows to point to the new key.  (There probably aren't any;
3875 -- this is just a precaution against possible local modifications.)
3876
3877 UPDATE action_trigger.event_definition
3878 SET hook = 'acqpo.activated'
3879 WHERE hook = 'format.po.jedi';
3880
3881 INSERT INTO action_trigger.reactor (module, description) VALUES (
3882     'AstCall', 'Possibly place a phone call with Asterisk'
3883 );
3884
3885 INSERT INTO
3886     action_trigger.event_definition (
3887         id, active, owner, name, hook, validator, reactor,
3888         cleanup_success, cleanup_failure, delay, delay_field, group_field,
3889         max_delay, granularity, usr_field, opt_in_setting, template
3890     ) VALUES (
3891         24,
3892         FALSE,
3893         1,
3894         'Telephone Overdue Notice',
3895         'checkout.due', 'NOOP_True', 'AstCall',
3896         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
3897         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
3898         $$
3899 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
3900 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
3901 Channel: [% channel_prefix %]/[% country %][% phone %]
3902 Context: overdue-test
3903 MaxRetries: 1
3904 RetryTime: 60
3905 WaitTime: 30
3906 Extension: 10
3907 Archive: 1
3908 Set: eg_user_id=[% target.0.usr.id %]
3909 Set: items=[% target.size %]
3910 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
3911 $$
3912     );
3913
3914 INSERT INTO
3915     action_trigger.environment (id, event_def, path)
3916     VALUES
3917         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
3918         (DEFAULT, 24, 'usr')
3919     ;
3920
3921 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3922         'circ.format.history.email',
3923         'circ', 
3924         oils_i18n_gettext(
3925             'circ.format.history.email',
3926             'An email has been requested for a circ history.',
3927             'ath',
3928             'description'
3929         ), 
3930         FALSE
3931     )
3932     ,(
3933         'circ.format.history.print',
3934         'circ', 
3935         oils_i18n_gettext(
3936             'circ.format.history.print',
3937             'A circ history needs to be formatted for printing.',
3938             'ath',
3939             'description'
3940         ), 
3941         FALSE
3942     )
3943     ,(
3944         'ahr.format.history.email',
3945         'ahr', 
3946         oils_i18n_gettext(
3947             'ahr.format.history.email',
3948             'An email has been requested for a hold request history.',
3949             'ath',
3950             'description'
3951         ), 
3952         FALSE
3953     )
3954     ,(
3955         'ahr.format.history.print',
3956         'ahr', 
3957         oils_i18n_gettext(
3958             'ahr.format.history.print',
3959             'A hold request history needs to be formatted for printing.',
3960             'ath',
3961             'description'
3962         ), 
3963         FALSE
3964     )
3965
3966 ;
3967
3968 INSERT INTO action_trigger.event_definition (
3969         id,
3970         active,
3971         owner,
3972         name,
3973         hook,
3974         validator,
3975         reactor,
3976         group_field,
3977         granularity,
3978         template
3979     ) VALUES (
3980         25,
3981         TRUE,
3982         1,
3983         'circ.history.email',
3984         'circ.format.history.email',
3985         'NOOP_True',
3986         'SendEmail',
3987         'usr',
3988         NULL,
3989 $$
3990 [%- USE date -%]
3991 [%- SET user = target.0.usr -%]
3992 To: [%- params.recipient_email || user.email %]
3993 From: [%- params.sender_email || default_sender %]
3994 Subject: Circulation History
3995
3996     [% FOR circ IN target %]
3997             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
3998             Barcode: [% circ.target_copy.barcode %]
3999             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
4000             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
4001             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
4002     [% END %]
4003 $$
4004     )
4005     ,(
4006         26,
4007         TRUE,
4008         1,
4009         'circ.history.print',
4010         'circ.format.history.print',
4011         'NOOP_True',
4012         'ProcessTemplate',
4013         'usr',
4014         'print-on-demand',
4015 $$
4016 [%- USE date -%]
4017 <div>
4018     <style> li { padding: 8px; margin 5px; }</style>
4019     <div>[% date.format %]</div>
4020     <br/>
4021
4022     [% user.family_name %], [% user.first_given_name %]
4023     <ol>
4024     [% FOR circ IN target %]
4025         <li>
4026             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
4027             <div>Barcode: [% circ.target_copy.barcode %]</div>
4028             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
4029             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
4030             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
4031         </li>
4032     [% END %]
4033     </ol>
4034 </div>
4035 $$
4036     )
4037     ,(
4038         27,
4039         TRUE,
4040         1,
4041         'ahr.history.email',
4042         'ahr.format.history.email',
4043         'NOOP_True',
4044         'SendEmail',
4045         'usr',
4046         NULL,
4047 $$
4048 [%- USE date -%]
4049 [%- SET user = target.0.usr -%]
4050 To: [%- params.recipient_email || user.email %]
4051 From: [%- params.sender_email || default_sender %]
4052 Subject: Hold Request History
4053
4054     [% FOR hold IN target %]
4055             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
4056             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
4057             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
4058     [% END %]
4059 $$
4060     )
4061     ,(
4062         28,
4063         TRUE,
4064         1,
4065         'ahr.history.print',
4066         'ahr.format.history.print',
4067         'NOOP_True',
4068         'ProcessTemplate',
4069         'usr',
4070         'print-on-demand',
4071 $$
4072 [%- USE date -%]
4073 <div>
4074     <style> li { padding: 8px; margin 5px; }</style>
4075     <div>[% date.format %]</div>
4076     <br/>
4077
4078     [% user.family_name %], [% user.first_given_name %]
4079     <ol>
4080     [% FOR hold IN target %]
4081         <li>
4082             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
4083             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
4084             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
4085         </li>
4086     [% END %]
4087     </ol>
4088 </div>
4089 $$
4090     )
4091
4092 ;
4093
4094 INSERT INTO action_trigger.environment (
4095         event_def,
4096         path
4097     ) VALUES 
4098          ( 25, 'target_copy')
4099         ,( 25, 'usr' )
4100         ,( 26, 'target_copy' )
4101         ,( 26, 'usr' )
4102         ,( 27, 'current_copy' )
4103         ,( 27, 'usr' )
4104         ,( 28, 'current_copy' )
4105         ,( 28, 'usr' )
4106 ;
4107
4108 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4109         'money.format.payment_receipt.email',
4110         'mp', 
4111         oils_i18n_gettext(
4112             'money.format.payment_receipt.email',
4113             'An email has been requested for a payment receipt.',
4114             'ath',
4115             'description'
4116         ), 
4117         FALSE
4118     )
4119     ,(
4120         'money.format.payment_receipt.print',
4121         'mp', 
4122         oils_i18n_gettext(
4123             'money.format.payment_receipt.print',
4124             'A payment receipt needs to be formatted for printing.',
4125             'ath',
4126             'description'
4127         ), 
4128         FALSE
4129     )
4130 ;
4131
4132 INSERT INTO action_trigger.event_definition (
4133         id,
4134         active,
4135         owner,
4136         name,
4137         hook,
4138         validator,
4139         reactor,
4140         group_field,
4141         granularity,
4142         template
4143     ) VALUES (
4144         29,
4145         TRUE,
4146         1,
4147         'money.payment_receipt.email',
4148         'money.format.payment_receipt.email',
4149         'NOOP_True',
4150         'SendEmail',
4151         'xact.usr',
4152         NULL,
4153 $$
4154 [%- USE date -%]
4155 [%- SET user = target.0.xact.usr -%]
4156 To: [%- params.recipient_email || user.email %]
4157 From: [%- params.sender_email || default_sender %]
4158 Subject: Payment Receipt
4159
4160 [% date.format -%]
4161 [%- SET xact_mp_hash = {} -%]
4162 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
4163     [%- SET xact_id = mp.xact.id -%]
4164     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
4165     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
4166 [%- END -%]
4167 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
4168     [%- SET xact = xact_mp_hash.$xact_id.xact %]
4169 Transaction ID: [% xact_id %]
4170     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4171     [% ELSE %]Miscellaneous
4172     [% END %]
4173     Line item billings:
4174         [%- SET mb_type_hash = {} -%]
4175         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
4176             [%- IF mb.voided == 'f' -%]
4177                 [%- SET mb_type = mb.btype.id -%]
4178                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
4179                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
4180                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
4181                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
4182                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
4183             [%- END -%]
4184         [%- END -%]
4185         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
4186             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
4187                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4188                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4189             [%- ELSE -%][%# all other billings show individually %]
4190                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
4191                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4192                 [% END %]
4193             [% END %]
4194         [% END %]
4195     Line item payments:
4196         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4197             Payment ID: [% mp.id %]
4198                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4199                     [% CASE "cash_payment" %]cash
4200                     [% CASE "check_payment" %]check
4201                     [% CASE "credit_card_payment" %]credit card (
4202                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4203                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4204                         [% cc_chunks.last -%]
4205                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4206                     )
4207                     [% CASE "credit_payment" %]credit
4208                     [% CASE "forgive_payment" %]forgiveness
4209                     [% CASE "goods_payment" %]goods
4210                     [% CASE "work_payment" %]work
4211                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
4212         [% END %]
4213 [% END %]
4214 $$
4215     )
4216     ,(
4217         30,
4218         TRUE,
4219         1,
4220         'money.payment_receipt.print',
4221         'money.format.payment_receipt.print',
4222         'NOOP_True',
4223         'ProcessTemplate',
4224         'xact.usr',
4225         'print-on-demand',
4226 $$
4227 [%- USE date -%][%- SET user = target.0.xact.usr -%]
4228 <div style="li { padding: 8px; margin 5px; }">
4229     <div>[% date.format %]</div><br/>
4230     <ol>
4231     [% SET xact_mp_hash = {} %]
4232     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
4233         [% SET xact_id = mp.xact.id %]
4234         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
4235         [% xact_mp_hash.$xact_id.payments.push(mp) %]
4236     [% END %]
4237     [% FOR xact_id IN xact_mp_hash.keys.sort %]
4238         [% SET xact = xact_mp_hash.$xact_id.xact %]
4239         <li>Transaction ID: [% xact_id %]
4240             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4241             [% ELSE %]Miscellaneous
4242             [% END %]
4243             Line item billings:<ol>
4244                 [% SET mb_type_hash = {} %]
4245                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
4246                     [% IF mb.voided == 'f' %]
4247                         [% SET mb_type = mb.btype.id %]
4248                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
4249                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
4250                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
4251                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
4252                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
4253                     [% END %]
4254                 [% END %]
4255                 [% FOR mb_type IN mb_type_hash.keys.sort %]
4256                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
4257                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4258                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4259                     [% ELSE %][%# all other billings show individually %]
4260                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
4261                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4262                         [% END %]
4263                     [% END %]</li>
4264                 [% END %]
4265             </ol>
4266             Line item payments:<ol>
4267                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4268                     <li>Payment ID: [% mp.id %]
4269                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4270                             [% CASE "cash_payment" %]cash
4271                             [% CASE "check_payment" %]check
4272                             [% CASE "credit_card_payment" %]credit card (
4273                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4274                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4275                                 [% cc_chunks.last -%]
4276                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4277                             )
4278                             [% CASE "credit_payment" %]credit
4279                             [% CASE "forgive_payment" %]forgiveness
4280                             [% CASE "goods_payment" %]goods
4281                             [% CASE "work_payment" %]work
4282                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
4283                     </li>
4284                 [% END %]
4285             </ol>
4286         </li>
4287     [% END %]
4288     </ol>
4289 </div>
4290 $$
4291     )
4292 ;
4293
4294 INSERT INTO action_trigger.environment (
4295         event_def,
4296         path
4297     ) VALUES -- for fleshing mp objects
4298          ( 29, 'xact')
4299         ,( 29, 'xact.usr')
4300         ,( 29, 'xact.grocery' )
4301         ,( 29, 'xact.circulation' )
4302         ,( 29, 'xact.summary' )
4303         ,( 30, 'xact')
4304         ,( 30, 'xact.usr')
4305         ,( 30, 'xact.grocery' )
4306         ,( 30, 'xact.circulation' )
4307         ,( 30, 'xact.summary' )
4308 ;
4309
4310 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
4311     'DeleteTempBiblioBucket',
4312     oils_i18n_gettext(
4313         'DeleteTempBiblioBucket',
4314         'Deletes a cbreb object used as a target if it has a btype of "temp"',
4315         'atclean',
4316         'description'
4317     )
4318 );
4319
4320 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4321         'biblio.format.record_entry.email',
4322         'cbreb', 
4323         oils_i18n_gettext(
4324             'biblio.format.record_entry.email',
4325             'An email has been requested for one or more biblio record entries.',
4326             'ath',
4327             'description'
4328         ), 
4329         FALSE
4330     )
4331     ,(
4332         'biblio.format.record_entry.print',
4333         'cbreb', 
4334         oils_i18n_gettext(
4335             'biblio.format.record_entry.print',
4336             'One or more biblio record entries need to be formatted for printing.',
4337             'ath',
4338             'description'
4339         ), 
4340         FALSE
4341     )
4342 ;
4343
4344 INSERT INTO action_trigger.event_definition (
4345         id,
4346         active,
4347         owner,
4348         name,
4349         hook,
4350         validator,
4351         reactor,
4352         cleanup_success,
4353         cleanup_failure,
4354         group_field,
4355         granularity,
4356         template
4357     ) VALUES (
4358         31,
4359         TRUE,
4360         1,
4361         'biblio.record_entry.email',
4362         'biblio.format.record_entry.email',
4363         'NOOP_True',
4364         'SendEmail',
4365         'DeleteTempBiblioBucket',
4366         'DeleteTempBiblioBucket',
4367         'owner',
4368         NULL,
4369 $$
4370 [%- USE date -%]
4371 [%- SET user = target.0.owner -%]
4372 To: [%- params.recipient_email || user.email %]
4373 From: [%- params.sender_email || default_sender %]
4374 Subject: Bibliographic Records
4375
4376     [% FOR cbreb IN target %]
4377     [% FOR cbrebi IN cbreb.items %]
4378         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
4379         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
4380         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
4381         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4382
4383     [% END %]
4384     [% END %]
4385 $$
4386     )
4387     ,(
4388         32,
4389         TRUE,
4390         1,
4391         'biblio.record_entry.print',
4392         'biblio.format.record_entry.print',
4393         'NOOP_True',
4394         'ProcessTemplate',
4395         'DeleteTempBiblioBucket',
4396         'DeleteTempBiblioBucket',
4397         'owner',
4398         'print-on-demand',
4399 $$
4400 [%- USE date -%]
4401 <div>
4402     <style> li { padding: 8px; margin 5px; }</style>
4403     <ol>
4404     [% FOR cbreb IN target %]
4405     [% FOR cbrebi IN cbreb.items %]
4406         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
4407             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
4408             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
4409             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4410         </li>
4411     [% END %]
4412     [% END %]
4413     </ol>
4414 </div>
4415 $$
4416     )
4417 ;
4418
4419 INSERT INTO action_trigger.environment (
4420         event_def,
4421         path
4422     ) VALUES -- for fleshing cbreb objects
4423          ( 31, 'owner' )
4424         ,( 31, 'items' )
4425         ,( 31, 'items.target_biblio_record_entry' )
4426         ,( 31, 'items.target_biblio_record_entry.simple_record' )
4427         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
4428         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
4429         ,( 31, 'items.target_biblio_record_entry.notes' )
4430         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
4431         ,( 32, 'owner' )
4432         ,( 32, 'items' )
4433         ,( 32, 'items.target_biblio_record_entry' )
4434         ,( 32, 'items.target_biblio_record_entry.simple_record' )
4435         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
4436         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
4437         ,( 32, 'items.target_biblio_record_entry.notes' )
4438         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
4439 ;
4440
4441 INSERT INTO action_trigger.environment (
4442         event_def,
4443         path
4444     ) VALUES -- for fleshing mp objects
4445          ( 29, 'credit_card_payment')
4446         ,( 29, 'xact.billings')
4447         ,( 29, 'xact.billings.btype')
4448         ,( 30, 'credit_card_payment')
4449         ,( 30, 'xact.billings')
4450         ,( 30, 'xact.billings.btype')
4451 ;
4452
4453 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
4454     (   'circ.format.missing_pieces.slip.print',
4455         'circ', 
4456         oils_i18n_gettext(
4457             'circ.format.missing_pieces.slip.print',
4458             'A missing pieces slip needs to be formatted for printing.',
4459             'ath',
4460             'description'
4461         ), 
4462         FALSE
4463     )
4464     ,(  'circ.format.missing_pieces.letter.print',
4465         'circ', 
4466         oils_i18n_gettext(
4467             'circ.format.missing_pieces.letter.print',
4468             'A missing pieces patron letter needs to be formatted for printing.',
4469             'ath',
4470             'description'
4471         ), 
4472         FALSE
4473     )
4474 ;
4475
4476 INSERT INTO action_trigger.event_definition (
4477         id,
4478         active,
4479         owner,
4480         name,
4481         hook,
4482         validator,
4483         reactor,
4484         group_field,
4485         granularity,
4486         template
4487     ) VALUES (
4488         33,
4489         TRUE,
4490         1,
4491         'circ.missing_pieces.slip.print',
4492         'circ.format.missing_pieces.slip.print',
4493         'NOOP_True',
4494         'ProcessTemplate',
4495         'usr',
4496         'print-on-demand',
4497 $$
4498 [%- USE date -%]
4499 [%- SET user = target.0.usr -%]
4500 <div style="li { padding: 8px; margin 5px; }">
4501     <div>[% date.format %]</div><br/>
4502     Missing pieces for:
4503     <ol>
4504     [% FOR circ IN target %]
4505         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
4506             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4507         </li>
4508     [% END %]
4509     </ol>
4510 </div>
4511 $$
4512     )
4513     ,(
4514         34,
4515         TRUE,
4516         1,
4517         'circ.missing_pieces.letter.print',
4518         'circ.format.missing_pieces.letter.print',
4519         'NOOP_True',
4520         'ProcessTemplate',
4521         'usr',
4522         'print-on-demand',
4523 $$
4524 [%- USE date -%]
4525 [%- SET user = target.0.usr -%]
4526 [% date.format %]
4527 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
4528
4529 We are missing pieces for the following returned items:
4530 [% FOR circ IN target %]
4531 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
4532 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4533 [% END %]
4534
4535 Please return these pieces as soon as possible.
4536
4537 Thanks!
4538
4539 Library Staff
4540 $$
4541     )
4542 ;
4543
4544 INSERT INTO action_trigger.environment (
4545         event_def,
4546         path
4547     ) VALUES -- for fleshing circ objects
4548          ( 33, 'usr')
4549         ,( 33, 'target_copy')
4550         ,( 33, 'target_copy.circ_lib')
4551         ,( 33, 'target_copy.circ_lib.mailing_address')
4552         ,( 33, 'target_copy.circ_lib.billing_address')
4553         ,( 33, 'target_copy.call_number')
4554         ,( 33, 'target_copy.call_number.owning_lib')
4555         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
4556         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
4557         ,( 33, 'circ_lib')
4558         ,( 33, 'circ_lib.mailing_address')
4559         ,( 33, 'circ_lib.billing_address')
4560         ,( 34, 'usr')
4561         ,( 34, 'target_copy')
4562         ,( 34, 'target_copy.circ_lib')
4563         ,( 34, 'target_copy.circ_lib.mailing_address')
4564         ,( 34, 'target_copy.circ_lib.billing_address')
4565         ,( 34, 'target_copy.call_number')
4566         ,( 34, 'target_copy.call_number.owning_lib')
4567         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
4568         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
4569         ,( 34, 'circ_lib')
4570         ,( 34, 'circ_lib.mailing_address')
4571         ,( 34, 'circ_lib.billing_address')
4572 ;
4573
4574 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
4575     VALUES (   
4576         'ahr.format.pull_list',
4577         'ahr', 
4578         oils_i18n_gettext(
4579             'ahr.format.pull_list',
4580             'Format holds pull list for printing',
4581             'ath',
4582             'description'
4583         ), 
4584         FALSE
4585     );
4586
4587 INSERT INTO action_trigger.event_definition (
4588         id,
4589         active,
4590         owner,
4591         name,
4592         hook,
4593         validator,
4594         reactor,
4595         group_field,
4596         granularity,
4597         template
4598     ) VALUES (
4599         35,
4600         TRUE,
4601         1,
4602         'Holds Pull List',
4603         'ahr.format.pull_list',
4604         'NOOP_True',
4605         'ProcessTemplate',
4606         'pickup_lib',
4607         'print-on-demand',
4608 $$
4609 [%- USE date -%]
4610 <style>
4611     table { border-collapse: collapse; } 
4612     td { padding: 5px; border-bottom: 1px solid #888; } 
4613     th { font-weight: bold; }
4614 </style>
4615 [% 
4616     # Sort the holds into copy-location buckets
4617     # In the main print loop, sort each bucket by callnumber before printing
4618     SET holds_list = [];
4619     SET loc_data = [];
4620     SET current_location = target.0.current_copy.location.id;
4621     FOR hold IN target;
4622         IF current_location != hold.current_copy.location.id;
4623             SET current_location = hold.current_copy.location.id;
4624             holds_list.push(loc_data);
4625             SET loc_data = [];
4626         END;
4627         SET hold_data = {
4628             'hold' => hold,
4629             'callnumber' => hold.current_copy.call_number.label
4630         };
4631         loc_data.push(hold_data);
4632     END;
4633     holds_list.push(loc_data)
4634 %]
4635 <table>
4636     <thead>
4637         <tr>
4638             <th>Title</th>
4639             <th>Author</th>
4640             <th>Shelving Location</th>
4641             <th>Call Number</th>
4642             <th>Barcode</th>
4643             <th>Patron</th>
4644         </tr>
4645     </thead>
4646     <tbody>
4647     [% FOR loc_data IN holds_list  %]
4648         [% FOR hold_data IN loc_data.sort('callnumber') %]
4649             [% 
4650                 SET hold = hold_data.hold;
4651                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
4652             %]
4653             <tr>
4654                 <td>[% copy_data.title | truncate %]</td>
4655                 <td>[% copy_data.author | truncate %]</td>
4656                 <td>[% hold.current_copy.location.name %]</td>
4657                 <td>[% hold.current_copy.call_number.label %]</td>
4658                 <td>[% hold.current_copy.barcode %]</td>
4659                 <td>[% hold.usr.card.barcode %]</td>
4660             </tr>
4661         [% END %]
4662     [% END %]
4663     <tbody>
4664 </table>
4665 $$
4666 );
4667
4668 INSERT INTO action_trigger.environment (
4669         event_def,
4670         path
4671     ) VALUES
4672         (35, 'current_copy.location'),
4673         (35, 'current_copy.call_number'),
4674         (35, 'usr.card'),
4675         (35, 'pickup_lib')
4676 ;
4677
4678 INSERT INTO action_trigger.validator (module, description) VALUES ( 
4679     'HoldIsCancelled', 
4680     oils_i18n_gettext( 
4681         'HoldIsCancelled', 
4682         'Check whether a hold request is cancelled.', 
4683         'atval', 
4684         'description' 
4685     ) 
4686 );
4687
4688 -- Create the query schema, and the tables and views therein
4689
4690 DROP SCHEMA IF EXISTS sql CASCADE;
4691 DROP SCHEMA IF EXISTS query CASCADE;
4692
4693 CREATE SCHEMA query;
4694
4695 CREATE TABLE query.datatype (
4696         id              SERIAL            PRIMARY KEY,
4697         datatype_name   TEXT              NOT NULL UNIQUE,
4698         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
4699         is_composite    BOOL              NOT NULL DEFAULT FALSE,
4700         CONSTRAINT qdt_comp_not_num CHECK
4701         ( is_numeric IS FALSE OR is_composite IS FALSE )
4702 );
4703
4704 -- Define the most common datatypes in query.datatype.  Note that none of
4705 -- these stock datatypes specifies a width or precision.
4706
4707 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
4708 -- room for more stock datatypes if we ever want to add them.
4709
4710 SELECT setval( 'query.datatype_id_seq', 1000 );
4711
4712 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4713   VALUES (1, 'SMALLINT', true);
4714  
4715 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4716   VALUES (2, 'INTEGER', true);
4717  
4718 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4719   VALUES (3, 'BIGINT', true);
4720  
4721 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4722   VALUES (4, 'DECIMAL', true);
4723  
4724 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4725   VALUES (5, 'NUMERIC', true);
4726  
4727 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4728   VALUES (6, 'REAL', true);
4729  
4730 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4731   VALUES (7, 'DOUBLE PRECISION', true);
4732  
4733 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4734   VALUES (8, 'SERIAL', true);
4735  
4736 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4737   VALUES (9, 'BIGSERIAL', true);
4738  
4739 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4740   VALUES (10, 'MONEY', false);
4741  
4742 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4743   VALUES (11, 'VARCHAR', false);
4744  
4745 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4746   VALUES (12, 'CHAR', false);
4747  
4748 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4749   VALUES (13, 'TEXT', false);
4750  
4751 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4752   VALUES (14, '"char"', false);
4753  
4754 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4755   VALUES (15, 'NAME', false);
4756  
4757 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4758   VALUES (16, 'BYTEA', false);
4759  
4760 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4761   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
4762  
4763 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4764   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
4765  
4766 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4767   VALUES (19, 'DATE', false);
4768  
4769 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4770   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
4771  
4772 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4773   VALUES (21, 'TIME WITH TIME ZONE', false);
4774  
4775 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4776   VALUES (22, 'INTERVAL', false);
4777  
4778 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4779   VALUES (23, 'BOOLEAN', false);
4780  
4781 CREATE TABLE query.subfield (
4782         id              SERIAL            PRIMARY KEY,
4783         composite_type  INT               NOT NULL
4784                                           REFERENCES query.datatype(id)
4785                                           ON DELETE CASCADE
4786                                           DEFERRABLE INITIALLY DEFERRED,
4787         seq_no          INT               NOT NULL
4788                                           CONSTRAINT qsf_pos_seq_no
4789                                           CHECK( seq_no > 0 ),
4790         subfield_type   INT               NOT NULL
4791                                           REFERENCES query.datatype(id)
4792                                           DEFERRABLE INITIALLY DEFERRED,
4793         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
4794 );
4795
4796 CREATE TABLE query.function_sig (
4797         id              SERIAL            PRIMARY KEY,
4798         function_name   TEXT              NOT NULL,
4799         return_type     INT               REFERENCES query.datatype(id)
4800                                           DEFERRABLE INITIALLY DEFERRED,
4801         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
4802         CONSTRAINT qfd_rtn_or_aggr CHECK
4803         ( return_type IS NULL OR is_aggregate = FALSE )
4804 );
4805
4806 CREATE INDEX query_function_sig_name_idx 
4807         ON query.function_sig (function_name);
4808
4809 CREATE TABLE query.function_param_def (
4810         id              SERIAL            PRIMARY KEY,
4811         function_id     INT               NOT NULL
4812                                           REFERENCES query.function_sig( id )
4813                                           ON DELETE CASCADE
4814                                           DEFERRABLE INITIALLY DEFERRED,
4815         seq_no          INT               NOT NULL
4816                                           CONSTRAINT qfpd_pos_seq_no CHECK
4817                                           ( seq_no > 0 ),
4818         datatype        INT               NOT NULL
4819                                           REFERENCES query.datatype( id )
4820                                           DEFERRABLE INITIALLY DEFERRED,
4821         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
4822 );
4823
4824 CREATE TABLE  query.stored_query (
4825         id            SERIAL         PRIMARY KEY,
4826         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
4827                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
4828         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
4829         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
4830         from_clause   INT            , --REFERENCES query.from_clause
4831                                      --DEFERRABLE INITIALLY DEFERRED,
4832         where_clause  INT            , --REFERENCES query.expression
4833                                      --DEFERRABLE INITIALLY DEFERRED,
4834         having_clause INT            , --REFERENCES query.expression
4835                                      --DEFERRABLE INITIALLY DEFERRED
4836         limit_count   INT            , --REFERENCES query.expression( id )
4837                                      --DEFERRABLE INITIALLY DEFERRED,
4838         offset_count  INT            --REFERENCES query.expression( id )
4839                                      --DEFERRABLE INITIALLY DEFERRED
4840 );
4841
4842 -- (Foreign keys to be defined later after other tables are created)
4843
4844 CREATE TABLE query.query_sequence (
4845         id              SERIAL            PRIMARY KEY,
4846         parent_query    INT               NOT NULL
4847                                           REFERENCES query.stored_query
4848                                                                           ON DELETE CASCADE
4849                                                                           DEFERRABLE INITIALLY DEFERRED,
4850         seq_no          INT               NOT NULL,
4851         child_query     INT               NOT NULL
4852                                           REFERENCES query.stored_query
4853                                                                           ON DELETE CASCADE
4854                                                                           DEFERRABLE INITIALLY DEFERRED,
4855         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
4856 );
4857
4858 CREATE TABLE query.bind_variable (
4859         name          TEXT             PRIMARY KEY,
4860         type          TEXT             NOT NULL
4861                                            CONSTRAINT bind_variable_type CHECK
4862                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
4863         description   TEXT             NOT NULL,
4864         default_value TEXT,            -- to be encoded in JSON
4865         label         TEXT             NOT NULL
4866 );
4867
4868 CREATE TABLE query.expression (
4869         id            SERIAL        PRIMARY KEY,
4870         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
4871                                     ( type IN (
4872                                     'xbet',    -- between
4873                                     'xbind',   -- bind variable
4874                                     'xbool',   -- boolean
4875                                     'xcase',   -- case
4876                                     'xcast',   -- cast
4877                                     'xcol',    -- column
4878                                     'xex',     -- exists
4879                                     'xfunc',   -- function
4880                                     'xin',     -- in
4881                                     'xisnull', -- is null
4882                                     'xnull',   -- null
4883                                     'xnum',    -- number
4884                                     'xop',     -- operator
4885                                     'xser',    -- series
4886                                     'xstr',    -- string
4887                                     'xsubq'    -- subquery
4888                                                                 ) ),
4889         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
4890         parent_expr   INT           REFERENCES query.expression
4891                                     ON DELETE CASCADE
4892                                     DEFERRABLE INITIALLY DEFERRED,
4893         seq_no        INT           NOT NULL DEFAULT 1,
4894         literal       TEXT,
4895         table_alias   TEXT,
4896         column_name   TEXT,
4897         left_operand  INT           REFERENCES query.expression
4898                                     DEFERRABLE INITIALLY DEFERRED,
4899         operator      TEXT,
4900         right_operand INT           REFERENCES query.expression
4901                                     DEFERRABLE INITIALLY DEFERRED,
4902         function_id   INT           REFERENCES query.function_sig
4903                                     DEFERRABLE INITIALLY DEFERRED,
4904         subquery      INT           REFERENCES query.stored_query
4905                                     DEFERRABLE INITIALLY DEFERRED,
4906         cast_type     INT           REFERENCES query.datatype
4907                                     DEFERRABLE INITIALLY DEFERRED,
4908         negate        BOOL          NOT NULL DEFAULT FALSE,
4909         bind_variable TEXT          REFERENCES query.bind_variable
4910                                         DEFERRABLE INITIALLY DEFERRED
4911 );
4912
4913 CREATE UNIQUE INDEX query_expr_parent_seq
4914         ON query.expression( parent_expr, seq_no )
4915         WHERE parent_expr IS NOT NULL;
4916
4917 -- Due to some circular references, the following foreign key definitions
4918 -- had to be deferred until query.expression existed:
4919
4920 ALTER TABLE query.stored_query
4921         ADD FOREIGN KEY ( where_clause )
4922         REFERENCES query.expression( id )
4923         DEFERRABLE INITIALLY DEFERRED;
4924
4925 ALTER TABLE query.stored_query
4926         ADD FOREIGN KEY ( having_clause )
4927         REFERENCES query.expression( id )
4928         DEFERRABLE INITIALLY DEFERRED;
4929
4930 ALTER TABLE query.stored_query
4931     ADD FOREIGN KEY ( limit_count )
4932     REFERENCES query.expression( id )
4933     DEFERRABLE INITIALLY DEFERRED;
4934
4935 ALTER TABLE query.stored_query
4936     ADD FOREIGN KEY ( offset_count )
4937     REFERENCES query.expression( id )
4938     DEFERRABLE INITIALLY DEFERRED;
4939
4940 CREATE TABLE query.case_branch (
4941         id            SERIAL        PRIMARY KEY,
4942         parent_expr   INT           NOT NULL REFERENCES query.expression
4943                                     ON DELETE CASCADE
4944                                     DEFERRABLE INITIALLY DEFERRED,
4945         seq_no        INT           NOT NULL,
4946         condition     INT           REFERENCES query.expression
4947                                     DEFERRABLE INITIALLY DEFERRED,
4948         result        INT           NOT NULL REFERENCES query.expression
4949                                     DEFERRABLE INITIALLY DEFERRED,
4950         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
4951 );
4952
4953 CREATE TABLE query.from_relation (
4954         id               SERIAL        PRIMARY KEY,
4955         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
4956                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
4957         table_name       TEXT,
4958         class_name       TEXT,
4959         subquery         INT           REFERENCES query.stored_query,
4960         function_call    INT           REFERENCES query.expression,
4961         table_alias      TEXT,
4962         parent_relation  INT           REFERENCES query.from_relation
4963                                        ON DELETE CASCADE
4964                                        DEFERRABLE INITIALLY DEFERRED,
4965         seq_no           INT           NOT NULL DEFAULT 1,
4966         join_type        TEXT          CONSTRAINT good_join_type CHECK (
4967                                            join_type IS NULL OR join_type IN
4968                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
4969                                        ),
4970         on_clause        INT           REFERENCES query.expression
4971                                        DEFERRABLE INITIALLY DEFERRED,
4972         CONSTRAINT join_or_core CHECK (
4973         ( parent_relation IS NULL AND join_type IS NULL
4974           AND on_clause IS NULL )
4975         OR
4976         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
4977           AND on_clause IS NOT NULL )
4978         )
4979 );
4980
4981 CREATE UNIQUE INDEX from_parent_seq
4982         ON query.from_relation( parent_relation, seq_no )
4983         WHERE parent_relation IS NOT NULL;
4984
4985 -- The following foreign key had to be deferred until
4986 -- query.from_relation existed
4987
4988 ALTER TABLE query.stored_query
4989         ADD FOREIGN KEY (from_clause)
4990         REFERENCES query.from_relation
4991         DEFERRABLE INITIALLY DEFERRED;
4992
4993 CREATE TABLE query.record_column (
4994         id            SERIAL            PRIMARY KEY,
4995         from_relation INT               NOT NULL REFERENCES query.from_relation
4996                                         ON DELETE CASCADE
4997                                         DEFERRABLE INITIALLY DEFERRED,
4998         seq_no        INT               NOT NULL,
4999         column_name   TEXT              NOT NULL,
5000         column_type   INT               NOT NULL REFERENCES query.datatype
5001                                         ON DELETE CASCADE
5002                                                                         DEFERRABLE INITIALLY DEFERRED,
5003         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
5004 );
5005
5006 CREATE TABLE query.select_item (
5007         id               SERIAL         PRIMARY KEY,
5008         stored_query     INT            NOT NULL REFERENCES query.stored_query
5009                                         ON DELETE CASCADE
5010                                         DEFERRABLE INITIALLY DEFERRED,
5011         seq_no           INT            NOT NULL,
5012         expression       INT            NOT NULL REFERENCES query.expression
5013                                         DEFERRABLE INITIALLY DEFERRED,
5014         column_alias     TEXT,
5015         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
5016         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
5017 );
5018
5019 CREATE TABLE query.order_by_item (
5020         id               SERIAL         PRIMARY KEY,
5021         stored_query     INT            NOT NULL REFERENCES query.stored_query
5022                                         ON DELETE CASCADE
5023                                         DEFERRABLE INITIALLY DEFERRED,
5024         seq_no           INT            NOT NULL,
5025         expression       INT            NOT NULL REFERENCES query.expression
5026                                         ON DELETE CASCADE
5027                                         DEFERRABLE INITIALLY DEFERRED,
5028         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
5029 );
5030
5031 ------------------------------------------------------------
5032 -- Create updatable views for different kinds of expressions
5033 ------------------------------------------------------------
5034
5035 -- Create updatable view for BETWEEN expressions
5036
5037 CREATE OR REPLACE VIEW query.expr_xbet AS
5038     SELECT
5039                 id,
5040                 parenthesize,
5041                 parent_expr,
5042                 seq_no,
5043                 left_operand,
5044                 negate
5045     FROM
5046         query.expression
5047     WHERE
5048         type = 'xbet';
5049
5050 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
5051     ON INSERT TO query.expr_xbet
5052     DO INSTEAD
5053     INSERT INTO query.expression (
5054                 id,
5055                 type,
5056                 parenthesize,
5057                 parent_expr,
5058                 seq_no,
5059                 left_operand,
5060                 negate
5061     ) VALUES (
5062         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5063         'xbet',
5064         COALESCE(NEW.parenthesize, FALSE),
5065         NEW.parent_expr,
5066         COALESCE(NEW.seq_no, 1),
5067                 NEW.left_operand,
5068                 COALESCE(NEW.negate, false)
5069     );
5070
5071 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
5072     ON UPDATE TO query.expr_xbet
5073     DO INSTEAD
5074     UPDATE query.expression SET
5075         id = NEW.id,
5076         parenthesize = NEW.parenthesize,
5077         parent_expr = NEW.parent_expr,
5078         seq_no = NEW.seq_no,
5079                 left_operand = NEW.left_operand,
5080                 negate = NEW.negate
5081     WHERE
5082         id = OLD.id;
5083
5084 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
5085     ON DELETE TO query.expr_xbet
5086     DO INSTEAD
5087     DELETE FROM query.expression WHERE id = OLD.id;
5088
5089 -- Create updatable view for bind variable expressions
5090
5091 CREATE OR REPLACE VIEW query.expr_xbind AS
5092     SELECT
5093                 id,
5094                 parenthesize,
5095                 parent_expr,
5096                 seq_no,
5097                 bind_variable
5098     FROM
5099         query.expression
5100     WHERE
5101         type = 'xbind';
5102
5103 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
5104     ON INSERT TO query.expr_xbind
5105     DO INSTEAD
5106     INSERT INTO query.expression (
5107                 id,
5108                 type,
5109                 parenthesize,
5110                 parent_expr,
5111                 seq_no,
5112                 bind_variable
5113     ) VALUES (
5114         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5115         'xbind',
5116         COALESCE(NEW.parenthesize, FALSE),
5117         NEW.parent_expr,
5118         COALESCE(NEW.seq_no, 1),
5119                 NEW.bind_variable
5120     );
5121
5122 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
5123     ON UPDATE TO query.expr_xbind
5124     DO INSTEAD
5125     UPDATE query.expression SET
5126         id = NEW.id,
5127         parenthesize = NEW.parenthesize,
5128         parent_expr = NEW.parent_expr,
5129         seq_no = NEW.seq_no,
5130                 bind_variable = NEW.bind_variable
5131     WHERE
5132         id = OLD.id;
5133
5134 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
5135     ON DELETE TO query.expr_xbind
5136     DO INSTEAD
5137     DELETE FROM query.expression WHERE id = OLD.id;
5138
5139 -- Create updatable view for boolean expressions
5140
5141 CREATE OR REPLACE VIEW query.expr_xbool AS
5142     SELECT
5143                 id,
5144                 parenthesize,
5145                 parent_expr,
5146                 seq_no,
5147                 literal,
5148                 negate
5149     FROM
5150         query.expression
5151     WHERE
5152         type = 'xbool';
5153
5154 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
5155     ON INSERT TO query.expr_xbool
5156     DO INSTEAD
5157     INSERT INTO query.expression (
5158                 id,
5159                 type,
5160                 parenthesize,
5161                 parent_expr,
5162                 seq_no,
5163                 literal,
5164                 negate
5165     ) VALUES (
5166         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5167         'xbool',
5168         COALESCE(NEW.parenthesize, FALSE),
5169         NEW.parent_expr,
5170         COALESCE(NEW.seq_no, 1),
5171         NEW.literal,
5172                 COALESCE(NEW.negate, false)
5173     );
5174
5175 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
5176     ON UPDATE TO query.expr_xbool
5177     DO INSTEAD
5178     UPDATE query.expression SET
5179         id = NEW.id,
5180         parenthesize = NEW.parenthesize,
5181         parent_expr = NEW.parent_expr,
5182         seq_no = NEW.seq_no,
5183         literal = NEW.literal,
5184                 negate = NEW.negate
5185     WHERE
5186         id = OLD.id;
5187
5188 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
5189     ON DELETE TO query.expr_xbool
5190     DO INSTEAD
5191     DELETE FROM query.expression WHERE id = OLD.id;
5192
5193 -- Create updatable view for CASE expressions
5194
5195 CREATE OR REPLACE VIEW query.expr_xcase AS
5196     SELECT
5197                 id,
5198                 parenthesize,
5199                 parent_expr,
5200                 seq_no,
5201                 left_operand,
5202                 negate
5203     FROM
5204         query.expression
5205     WHERE
5206         type = 'xcase';
5207
5208 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
5209     ON INSERT TO query.expr_xcase
5210     DO INSTEAD
5211     INSERT INTO query.expression (
5212                 id,
5213                 type,
5214                 parenthesize,
5215                 parent_expr,
5216                 seq_no,
5217                 left_operand,
5218                 negate
5219     ) VALUES (
5220         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5221         'xcase',
5222         COALESCE(NEW.parenthesize, FALSE),
5223         NEW.parent_expr,
5224         COALESCE(NEW.seq_no, 1),
5225                 NEW.left_operand,
5226                 COALESCE(NEW.negate, false)
5227     );
5228
5229 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
5230     ON UPDATE TO query.expr_xcase
5231     DO INSTEAD
5232     UPDATE query.expression SET
5233         id = NEW.id,
5234         parenthesize = NEW.parenthesize,
5235         parent_expr = NEW.parent_expr,
5236         seq_no = NEW.seq_no,
5237                 left_operand = NEW.left_operand,
5238                 negate = NEW.negate
5239     WHERE
5240         id = OLD.id;
5241
5242 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
5243     ON DELETE TO query.expr_xcase
5244     DO INSTEAD
5245     DELETE FROM query.expression WHERE id = OLD.id;
5246
5247 -- Create updatable view for cast expressions
5248
5249 CREATE OR REPLACE VIEW query.expr_xcast AS
5250     SELECT
5251                 id,
5252                 parenthesize,
5253                 parent_expr,
5254                 seq_no,
5255                 left_operand,
5256                 cast_type,
5257                 negate
5258     FROM
5259         query.expression
5260     WHERE
5261         type = 'xcast';
5262
5263 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
5264     ON INSERT TO query.expr_xcast
5265     DO INSTEAD
5266     INSERT INTO query.expression (
5267         id,
5268         type,
5269         parenthesize,
5270         parent_expr,
5271         seq_no,
5272         left_operand,
5273         cast_type,
5274         negate
5275     ) VALUES (
5276         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5277         'xcast',
5278         COALESCE(NEW.parenthesize, FALSE),
5279         NEW.parent_expr,
5280         COALESCE(NEW.seq_no, 1),
5281         NEW.left_operand,
5282         NEW.cast_type,
5283         COALESCE(NEW.negate, false)
5284     );
5285
5286 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
5287     ON UPDATE TO query.expr_xcast
5288     DO INSTEAD
5289     UPDATE query.expression SET
5290         id = NEW.id,
5291         parenthesize = NEW.parenthesize,
5292         parent_expr = NEW.parent_expr,
5293         seq_no = NEW.seq_no,
5294                 left_operand = NEW.left_operand,
5295                 cast_type = NEW.cast_type,
5296                 negate = NEW.negate
5297     WHERE
5298         id = OLD.id;
5299
5300 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
5301     ON DELETE TO query.expr_xcast
5302     DO INSTEAD
5303     DELETE FROM query.expression WHERE id = OLD.id;
5304
5305 -- Create updatable view for column expressions
5306
5307 CREATE OR REPLACE VIEW query.expr_xcol AS
5308     SELECT
5309                 id,
5310                 parenthesize,
5311                 parent_expr,
5312                 seq_no,
5313                 table_alias,
5314                 column_name,
5315                 negate
5316     FROM
5317         query.expression
5318     WHERE
5319         type = 'xcol';
5320
5321 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
5322     ON INSERT TO query.expr_xcol
5323     DO INSTEAD
5324     INSERT INTO query.expression (
5325                 id,
5326                 type,
5327                 parenthesize,
5328                 parent_expr,
5329                 seq_no,
5330                 table_alias,
5331                 column_name,
5332                 negate
5333     ) VALUES (
5334         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5335         'xcol',
5336         COALESCE(NEW.parenthesize, FALSE),
5337         NEW.parent_expr,
5338         COALESCE(NEW.seq_no, 1),
5339                 NEW.table_alias,
5340                 NEW.column_name,
5341                 COALESCE(NEW.negate, false)
5342     );
5343
5344 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
5345     ON UPDATE TO query.expr_xcol
5346     DO INSTEAD
5347     UPDATE query.expression SET
5348         id = NEW.id,
5349         parenthesize = NEW.parenthesize,
5350         parent_expr = NEW.parent_expr,
5351         seq_no = NEW.seq_no,
5352                 table_alias = NEW.table_alias,
5353                 column_name = NEW.column_name,
5354                 negate = NEW.negate
5355     WHERE
5356         id = OLD.id;
5357
5358 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
5359     ON DELETE TO query.expr_xcol
5360     DO INSTEAD
5361     DELETE FROM query.expression WHERE id = OLD.id;
5362
5363 -- Create updatable view for EXISTS expressions
5364
5365 CREATE OR REPLACE VIEW query.expr_xex AS
5366     SELECT
5367                 id,
5368                 parenthesize,
5369                 parent_expr,
5370                 seq_no,
5371                 subquery,
5372                 negate
5373     FROM
5374         query.expression
5375     WHERE
5376         type = 'xex';
5377
5378 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
5379     ON INSERT TO query.expr_xex
5380     DO INSTEAD
5381     INSERT INTO query.expression (
5382                 id,
5383                 type,
5384                 parenthesize,
5385                 parent_expr,
5386                 seq_no,
5387                 subquery,
5388                 negate
5389     ) VALUES (
5390         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5391         'xex',
5392         COALESCE(NEW.parenthesize, FALSE),
5393         NEW.parent_expr,
5394         COALESCE(NEW.seq_no, 1),
5395                 NEW.subquery,
5396                 COALESCE(NEW.negate, false)
5397     );
5398
5399 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
5400     ON UPDATE TO query.expr_xex
5401     DO INSTEAD
5402     UPDATE query.expression SET
5403         id = NEW.id,
5404         parenthesize = NEW.parenthesize,
5405         parent_expr = NEW.parent_expr,
5406         seq_no = NEW.seq_no,
5407                 subquery = NEW.subquery,
5408                 negate = NEW.negate
5409     WHERE
5410         id = OLD.id;
5411
5412 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
5413     ON DELETE TO query.expr_xex
5414     DO INSTEAD
5415     DELETE FROM query.expression WHERE id = OLD.id;
5416
5417 -- Create updatable view for function call expressions
5418
5419 CREATE OR REPLACE VIEW query.expr_xfunc AS
5420     SELECT
5421         id,
5422         parenthesize,
5423         parent_expr,
5424         seq_no,
5425         column_name,
5426         function_id,
5427         negate
5428     FROM
5429         query.expression
5430     WHERE
5431         type = 'xfunc';
5432
5433 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
5434     ON INSERT TO query.expr_xfunc
5435     DO INSTEAD
5436     INSERT INTO query.expression (
5437         id,
5438         type,
5439         parenthesize,
5440         parent_expr,
5441         seq_no,
5442         column_name,
5443         function_id,
5444         negate
5445     ) VALUES (
5446         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5447         'xfunc',
5448         COALESCE(NEW.parenthesize, FALSE),
5449         NEW.parent_expr,
5450         COALESCE(NEW.seq_no, 1),
5451         NEW.column_name,
5452         NEW.function_id,
5453         COALESCE(NEW.negate, false)
5454     );
5455
5456 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
5457     ON UPDATE TO query.expr_xfunc
5458     DO INSTEAD
5459     UPDATE query.expression SET
5460         id = NEW.id,
5461         parenthesize = NEW.parenthesize,
5462         parent_expr = NEW.parent_expr,
5463         seq_no = NEW.seq_no,
5464         column_name = NEW.column_name,
5465         function_id = NEW.function_id,
5466         negate = NEW.negate
5467     WHERE
5468         id = OLD.id;
5469
5470 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
5471     ON DELETE TO query.expr_xfunc
5472     DO INSTEAD
5473     DELETE FROM query.expression WHERE id = OLD.id;
5474
5475 -- Create updatable view for IN expressions
5476
5477 CREATE OR REPLACE VIEW query.expr_xin AS
5478     SELECT
5479                 id,
5480                 parenthesize,
5481                 parent_expr,
5482                 seq_no,
5483                 left_operand,
5484                 subquery,
5485                 negate
5486     FROM
5487         query.expression
5488     WHERE
5489         type = 'xin';
5490
5491 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
5492     ON INSERT TO query.expr_xin
5493     DO INSTEAD
5494     INSERT INTO query.expression (
5495                 id,
5496                 type,
5497                 parenthesize,
5498                 parent_expr,
5499                 seq_no,
5500                 left_operand,
5501                 subquery,
5502                 negate
5503     ) VALUES (
5504         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5505         'xin',
5506         COALESCE(NEW.parenthesize, FALSE),
5507         NEW.parent_expr,
5508         COALESCE(NEW.seq_no, 1),
5509                 NEW.left_operand,
5510                 NEW.subquery,
5511                 COALESCE(NEW.negate, false)
5512     );
5513
5514 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
5515     ON UPDATE TO query.expr_xin
5516     DO INSTEAD
5517     UPDATE query.expression SET
5518         id = NEW.id,
5519         parenthesize = NEW.parenthesize,
5520         parent_expr = NEW.parent_expr,
5521         seq_no = NEW.seq_no,
5522                 left_operand = NEW.left_operand,
5523                 subquery = NEW.subquery,
5524                 negate = NEW.negate
5525     WHERE
5526         id = OLD.id;
5527
5528 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
5529     ON DELETE TO query.expr_xin
5530     DO INSTEAD
5531     DELETE FROM query.expression WHERE id = OLD.id;
5532
5533 -- Create updatable view for IS NULL expressions
5534
5535 CREATE OR REPLACE VIEW query.expr_xisnull AS
5536     SELECT
5537                 id,
5538                 parenthesize,
5539                 parent_expr,
5540                 seq_no,
5541                 left_operand,
5542                 negate
5543     FROM
5544         query.expression
5545     WHERE
5546         type = 'xisnull';
5547
5548 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
5549     ON INSERT TO query.expr_xisnull
5550     DO INSTEAD
5551     INSERT INTO query.expression (
5552                 id,
5553                 type,
5554                 parenthesize,
5555                 parent_expr,
5556                 seq_no,
5557                 left_operand,
5558                 negate
5559     ) VALUES (
5560         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5561         'xisnull',
5562         COALESCE(NEW.parenthesize, FALSE),
5563         NEW.parent_expr,
5564         COALESCE(NEW.seq_no, 1),
5565                 NEW.left_operand,
5566                 COALESCE(NEW.negate, false)
5567     );
5568
5569 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
5570     ON UPDATE TO query.expr_xisnull
5571     DO INSTEAD
5572     UPDATE query.expression SET
5573         id = NEW.id,
5574         parenthesize = NEW.parenthesize,
5575         parent_expr = NEW.parent_expr,
5576         seq_no = NEW.seq_no,
5577                 left_operand = NEW.left_operand,
5578                 negate = NEW.negate
5579     WHERE
5580         id = OLD.id;
5581
5582 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
5583     ON DELETE TO query.expr_xisnull
5584     DO INSTEAD
5585     DELETE FROM query.expression WHERE id = OLD.id;
5586
5587 -- Create updatable view for NULL expressions
5588
5589 CREATE OR REPLACE VIEW query.expr_xnull AS
5590     SELECT
5591                 id,
5592                 parenthesize,
5593                 parent_expr,
5594                 seq_no,
5595                 negate
5596     FROM
5597         query.expression
5598     WHERE
5599         type = 'xnull';
5600
5601 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
5602     ON INSERT TO query.expr_xnull
5603     DO INSTEAD
5604     INSERT INTO query.expression (
5605                 id,
5606                 type,
5607                 parenthesize,
5608                 parent_expr,
5609                 seq_no,
5610                 negate
5611     ) VALUES (
5612         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5613         'xnull',
5614         COALESCE(NEW.parenthesize, FALSE),
5615         NEW.parent_expr,
5616         COALESCE(NEW.seq_no, 1),
5617                 COALESCE(NEW.negate, false)
5618     );
5619
5620 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
5621     ON UPDATE TO query.expr_xnull
5622     DO INSTEAD
5623     UPDATE query.expression SET
5624         id = NEW.id,
5625         parenthesize = NEW.parenthesize,
5626         parent_expr = NEW.parent_expr,
5627         seq_no = NEW.seq_no,
5628                 negate = NEW.negate
5629     WHERE
5630         id = OLD.id;
5631
5632 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
5633     ON DELETE TO query.expr_xnull
5634     DO INSTEAD
5635     DELETE FROM query.expression WHERE id = OLD.id;
5636
5637 -- Create updatable view for numeric literal expressions
5638
5639 CREATE OR REPLACE VIEW query.expr_xnum AS
5640     SELECT
5641                 id,
5642                 parenthesize,
5643                 parent_expr,
5644                 seq_no,
5645                 literal
5646     FROM
5647         query.expression
5648     WHERE
5649         type = 'xnum';
5650
5651 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
5652     ON INSERT TO query.expr_xnum
5653     DO INSTEAD
5654     INSERT INTO query.expression (
5655                 id,
5656                 type,
5657                 parenthesize,
5658                 parent_expr,
5659                 seq_no,
5660                 literal
5661     ) VALUES (
5662         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5663         'xnum',
5664         COALESCE(NEW.parenthesize, FALSE),
5665         NEW.parent_expr,
5666         COALESCE(NEW.seq_no, 1),
5667         NEW.literal
5668     );
5669
5670 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
5671     ON UPDATE TO query.expr_xnum
5672     DO INSTEAD
5673     UPDATE query.expression SET
5674         id = NEW.id,
5675         parenthesize = NEW.parenthesize,
5676         parent_expr = NEW.parent_expr,
5677         seq_no = NEW.seq_no,
5678         literal = NEW.literal
5679     WHERE
5680         id = OLD.id;
5681
5682 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
5683     ON DELETE TO query.expr_xnum
5684     DO INSTEAD
5685     DELETE FROM query.expression WHERE id = OLD.id;
5686
5687 -- Create updatable view for operator expressions
5688
5689 CREATE OR REPLACE VIEW query.expr_xop AS
5690     SELECT
5691                 id,
5692                 parenthesize,
5693                 parent_expr,
5694                 seq_no,
5695                 left_operand,
5696                 operator,
5697                 right_operand,
5698                 negate
5699     FROM
5700         query.expression
5701     WHERE
5702         type = 'xop';
5703
5704 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
5705     ON INSERT TO query.expr_xop
5706     DO INSTEAD
5707     INSERT INTO query.expression (
5708                 id,
5709                 type,
5710                 parenthesize,
5711                 parent_expr,
5712                 seq_no,
5713                 left_operand,
5714                 operator,
5715                 right_operand,
5716                 negate
5717     ) VALUES (
5718         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5719         'xop',
5720         COALESCE(NEW.parenthesize, FALSE),
5721         NEW.parent_expr,
5722         COALESCE(NEW.seq_no, 1),
5723                 NEW.left_operand,
5724                 NEW.operator,
5725                 NEW.right_operand,
5726                 COALESCE(NEW.negate, false)
5727     );
5728
5729 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
5730     ON UPDATE TO query.expr_xop
5731     DO INSTEAD
5732     UPDATE query.expression SET
5733         id = NEW.id,
5734         parenthesize = NEW.parenthesize,
5735         parent_expr = NEW.parent_expr,
5736         seq_no = NEW.seq_no,
5737                 left_operand = NEW.left_operand,
5738                 operator = NEW.operator,
5739                 right_operand = NEW.right_operand,
5740                 negate = NEW.negate
5741     WHERE
5742         id = OLD.id;
5743
5744 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
5745     ON DELETE TO query.expr_xop
5746     DO INSTEAD
5747     DELETE FROM query.expression WHERE id = OLD.id;
5748
5749 -- Create updatable view for series expressions
5750 -- i.e. series of expressions separated by operators
5751
5752 CREATE OR REPLACE VIEW query.expr_xser AS
5753     SELECT
5754                 id,
5755                 parenthesize,
5756                 parent_expr,
5757                 seq_no,
5758                 operator,
5759                 negate
5760     FROM
5761         query.expression
5762     WHERE
5763         type = 'xser';
5764
5765 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
5766     ON INSERT TO query.expr_xser
5767     DO INSTEAD
5768     INSERT INTO query.expression (
5769                 id,
5770                 type,
5771                 parenthesize,
5772                 parent_expr,
5773                 seq_no,
5774                 operator,
5775                 negate
5776     ) VALUES (
5777         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5778         'xser',
5779         COALESCE(NEW.parenthesize, FALSE),
5780         NEW.parent_expr,
5781         COALESCE(NEW.seq_no, 1),
5782                 NEW.operator,
5783                 COALESCE(NEW.negate, false)
5784     );
5785
5786 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
5787     ON UPDATE TO query.expr_xser
5788     DO INSTEAD
5789     UPDATE query.expression SET
5790         id = NEW.id,
5791         parenthesize = NEW.parenthesize,
5792         parent_expr = NEW.parent_expr,
5793         seq_no = NEW.seq_no,
5794                 operator = NEW.operator,
5795                 negate = NEW.negate
5796     WHERE
5797         id = OLD.id;
5798
5799 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
5800     ON DELETE TO query.expr_xser
5801     DO INSTEAD
5802     DELETE FROM query.expression WHERE id = OLD.id;
5803
5804 -- Create updatable view for string literal expressions
5805
5806 CREATE OR REPLACE VIEW query.expr_xstr AS
5807     SELECT
5808         id,
5809         parenthesize,
5810         parent_expr,
5811         seq_no,
5812         literal
5813     FROM
5814         query.expression
5815     WHERE
5816         type = 'xstr';
5817
5818 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
5819     ON INSERT TO query.expr_xstr
5820     DO INSTEAD
5821     INSERT INTO query.expression (
5822         id,
5823         type,
5824         parenthesize,
5825         parent_expr,
5826         seq_no,
5827         literal
5828     ) VALUES (
5829         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5830         'xstr',
5831         COALESCE(NEW.parenthesize, FALSE),
5832         NEW.parent_expr,
5833         COALESCE(NEW.seq_no, 1),
5834         NEW.literal
5835     );
5836
5837 CREATE OR REPLACE RULE query_expr_string_update_rule AS
5838     ON UPDATE TO query.expr_xstr
5839     DO INSTEAD
5840     UPDATE query.expression SET
5841         id = NEW.id,
5842         parenthesize = NEW.parenthesize,
5843         parent_expr = NEW.parent_expr,
5844         seq_no = NEW.seq_no,
5845         literal = NEW.literal
5846     WHERE
5847         id = OLD.id;
5848
5849 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
5850     ON DELETE TO query.expr_xstr
5851     DO INSTEAD
5852     DELETE FROM query.expression WHERE id = OLD.id;
5853
5854 -- Create updatable view for subquery expressions
5855
5856 CREATE OR REPLACE VIEW query.expr_xsubq AS
5857     SELECT
5858                 id,
5859                 parenthesize,
5860                 parent_expr,
5861                 seq_no,
5862                 subquery,
5863                 negate
5864     FROM
5865         query.expression
5866     WHERE
5867         type = 'xsubq';
5868
5869 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
5870     ON INSERT TO query.expr_xsubq
5871     DO INSTEAD
5872     INSERT INTO query.expression (
5873                 id,
5874                 type,
5875                 parenthesize,
5876                 parent_expr,
5877                 seq_no,
5878                 subquery,
5879                 negate
5880     ) VALUES (
5881         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5882         'xsubq',
5883         COALESCE(NEW.parenthesize, FALSE),
5884         NEW.parent_expr,
5885         COALESCE(NEW.seq_no, 1),
5886                 NEW.subquery,
5887                 COALESCE(NEW.negate, false)
5888     );
5889
5890 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
5891     ON UPDATE TO query.expr_xsubq
5892     DO INSTEAD
5893     UPDATE query.expression SET
5894         id = NEW.id,
5895         parenthesize = NEW.parenthesize,
5896         parent_expr = NEW.parent_expr,
5897         seq_no = NEW.seq_no,
5898                 subquery = NEW.subquery,
5899                 negate = NEW.negate
5900     WHERE
5901         id = OLD.id;
5902
5903 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
5904     ON DELETE TO query.expr_xsubq
5905     DO INSTEAD
5906     DELETE FROM query.expression WHERE id = OLD.id;
5907
5908 CREATE TABLE action.fieldset (
5909     id              SERIAL          PRIMARY KEY,
5910     owner           INT             NOT NULL REFERENCES actor.usr (id)
5911                                     DEFERRABLE INITIALLY DEFERRED,
5912     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
5913                                     DEFERRABLE INITIALLY DEFERRED,
5914     status          TEXT            NOT NULL
5915                                     CONSTRAINT valid_status CHECK ( status in
5916                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
5917     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
5918     scheduled_time  TIMESTAMPTZ,
5919     applied_time    TIMESTAMPTZ,
5920     classname       TEXT            NOT NULL, -- an IDL class name
5921     name            TEXT            NOT NULL,
5922     stored_query    INT             REFERENCES query.stored_query (id)
5923                                     DEFERRABLE INITIALLY DEFERRED,
5924     pkey_value      TEXT,
5925     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
5926     CONSTRAINT fieldset_one_or_the_other CHECK (
5927         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
5928         (pkey_value IS NOT NULL AND stored_query IS NULL)
5929     )
5930     -- the CHECK constraint means we can update the fields for a single
5931     -- row without all the extra overhead involved in a query
5932 );
5933
5934 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
5935 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
5936
5937 CREATE TABLE action.fieldset_col_val (
5938     id              SERIAL  PRIMARY KEY,
5939     fieldset        INT     NOT NULL REFERENCES action.fieldset
5940                                          ON DELETE CASCADE
5941                                          DEFERRABLE INITIALLY DEFERRED,
5942     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
5943     val             TEXT,              -- value for the column ... NULL means, well, NULL
5944     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
5945 );
5946
5947 CREATE OR REPLACE FUNCTION action.apply_fieldset(
5948         fieldset_id IN INT,        -- id from action.fieldset
5949         table_name  IN TEXT,       -- table to be updated
5950         pkey_name   IN TEXT,       -- name of primary key column in that table
5951         query       IN TEXT        -- query constructed by qstore (for query-based
5952                                    --    fieldsets only; otherwise null
5953 )
5954 RETURNS TEXT AS $$
5955 DECLARE
5956         statement TEXT;
5957         fs_status TEXT;
5958         fs_pkey_value TEXT;
5959         fs_query TEXT;
5960         sep CHAR;
5961         status_code TEXT;
5962         msg TEXT;
5963         update_count INT;
5964         cv RECORD;
5965 BEGIN
5966         -- Sanity checks
5967         IF fieldset_id IS NULL THEN
5968                 RETURN 'Fieldset ID parameter is NULL';
5969         END IF;
5970         IF table_name IS NULL THEN
5971                 RETURN 'Table name parameter is NULL';
5972         END IF;
5973         IF pkey_name IS NULL THEN
5974                 RETURN 'Primary key name parameter is NULL';
5975         END IF;
5976         --
5977         statement := 'UPDATE ' || table_name || ' SET';
5978         --
5979         SELECT
5980                 status,
5981                 quote_literal( pkey_value )
5982         INTO
5983                 fs_status,
5984                 fs_pkey_value
5985         FROM
5986                 action.fieldset
5987         WHERE
5988                 id = fieldset_id;
5989         --
5990         IF fs_status IS NULL THEN
5991                 RETURN 'No fieldset found for id = ' || fieldset_id;
5992         ELSIF fs_status = 'APPLIED' THEN
5993                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
5994         END IF;
5995         --
5996         sep := '';
5997         FOR cv IN
5998                 SELECT  col,
5999                                 val
6000                 FROM    action.fieldset_col_val
6001                 WHERE   fieldset = fieldset_id
6002         LOOP
6003                 statement := statement || sep || ' ' || cv.col
6004                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
6005                 sep := ',';
6006         END LOOP;
6007         --
6008         IF sep = '' THEN
6009                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
6010         END IF;
6011         --
6012         -- Add the WHERE clause.  This differs according to whether it's a
6013         -- single-row fieldset or a query-based fieldset.
6014         --
6015         IF query IS NULL        AND fs_pkey_value IS NULL THEN
6016                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
6017         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
6018             fs_query := rtrim( query, ';' );
6019             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
6020                          || fs_query || ' );';
6021         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
6022                 statement := statement || ' WHERE ' || pkey_name || ' = '
6023                                      || fs_pkey_value || ';';
6024         ELSE  -- both are not null
6025                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
6026         END IF;
6027         --
6028         -- Execute the update
6029         --
6030         BEGIN
6031                 EXECUTE statement;
6032                 GET DIAGNOSTICS update_count = ROW_COUNT;
6033                 --
6034                 IF UPDATE_COUNT > 0 THEN
6035                         status_code := 'APPLIED';
6036                         msg := NULL;
6037                 ELSE
6038                         status_code := 'ERROR';
6039                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
6040         END IF;
6041         EXCEPTION WHEN OTHERS THEN
6042                 status_code := 'ERROR';
6043                 msg := 'Unable to apply fieldset ' || fieldset_id
6044                            || ': ' || sqlerrm;
6045         END;
6046         --
6047         -- Update fieldset status
6048         --
6049         UPDATE action.fieldset
6050         SET status       = status_code,
6051             applied_time = now()
6052         WHERE id = fieldset_id;
6053         --
6054         RETURN msg;
6055 END;
6056 $$ LANGUAGE plpgsql;
6057
6058 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
6059 /**
6060  * Applies a specified fieldset, using a supplied table name and primary
6061  * key name.  The query parameter should be non-null only for
6062  * query-based fieldsets.
6063  *
6064  * Returns NULL if successful, or an error message if not.
6065  */
6066 $$;
6067
6068 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
6069
6070 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
6071     SELECT  u.hold,
6072             c.circ_lib,
6073             count(*)
6074       FROM  action.unfulfilled_hold_list u
6075             JOIN asset.copy c ON (c.id = u.current_copy)
6076       GROUP BY 1,2;
6077
6078 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
6079     SELECT  hold,
6080             min(count)
6081       FROM  action.unfulfilled_hold_loops
6082       GROUP BY 1;
6083
6084 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
6085     SELECT  DISTINCT l.*
6086       FROM  action.unfulfilled_hold_loops l
6087             JOIN action.unfulfilled_hold_min_loop m USING (hold)
6088       WHERE l.count = m.min;
6089
6090 ALTER TABLE asset.copy
6091 ADD COLUMN dummy_isbn TEXT;
6092
6093 ALTER TABLE auditor.asset_copy_history
6094 ADD COLUMN dummy_isbn TEXT;
6095
6096 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
6097 -- Add corresponding new column to auditor.asset_copy_history
6098
6099 ALTER TABLE asset.copy
6100         ADD COLUMN status_changed_time TIMESTAMPTZ;
6101
6102 ALTER TABLE auditor.asset_copy_history
6103         ADD COLUMN status_changed_time TIMESTAMPTZ;
6104
6105 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
6106 RETURNS TRIGGER AS $$
6107 BEGIN
6108     IF NEW.status <> OLD.status THEN
6109         NEW.status_changed_time := now();
6110     END IF;
6111     RETURN NEW;
6112 END;
6113 $$ LANGUAGE plpgsql;
6114
6115 CREATE TRIGGER acp_status_changed_trig
6116         BEFORE UPDATE ON asset.copy
6117         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
6118
6119 ALTER TABLE asset.copy
6120 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6121
6122 ALTER TABLE auditor.asset_copy_history
6123 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6124
6125 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
6126 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
6127
6128 DROP INDEX IF EXISTS asset.copy_barcode_key;
6129 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
6130
6131 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
6132 -- AFTER INSERT OR UPDATE ON asset.copy
6133
6134 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
6135 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
6136
6137 -- Moke mostly parallel changes to action.circulation
6138 -- and action.aged_circulation
6139
6140 ALTER TABLE action.circulation
6141 ADD COLUMN workstation INT
6142     REFERENCES actor.workstation
6143         ON DELETE SET NULL
6144         DEFERRABLE INITIALLY DEFERRED;
6145
6146 ALTER TABLE action.aged_circulation
6147 ADD COLUMN workstation INT;
6148
6149 ALTER TABLE action.circulation
6150 ADD COLUMN parent_circ BIGINT
6151         REFERENCES action.circulation(id)
6152         DEFERRABLE INITIALLY DEFERRED;
6153
6154 CREATE UNIQUE INDEX circ_parent_idx
6155 ON action.circulation( parent_circ )
6156 WHERE parent_circ IS NOT NULL;
6157
6158 ALTER TABLE action.aged_circulation
6159 ADD COLUMN parent_circ BIGINT;
6160
6161 ALTER TABLE action.circulation
6162 ADD COLUMN checkin_workstation INT
6163         REFERENCES actor.workstation(id)
6164         ON DELETE SET NULL
6165         DEFERRABLE INITIALLY DEFERRED;
6166
6167 ALTER TABLE action.aged_circulation
6168 ADD COLUMN checkin_workstation INT;
6169
6170 ALTER TABLE action.circulation
6171 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6172
6173 ALTER TABLE action.aged_circulation
6174 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6175
6176 CREATE INDEX action_circulation_target_copy_idx
6177 ON action.circulation (target_copy);
6178
6179 CREATE INDEX action_aged_circulation_target_copy_idx
6180 ON action.aged_circulation (target_copy);
6181
6182 ALTER TABLE action.circulation
6183 DROP CONSTRAINT circulation_stop_fines_check;
6184
6185 ALTER TABLE action.circulation
6186         ADD CONSTRAINT circulation_stop_fines_check
6187         CHECK (stop_fines IN (
6188         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
6189
6190 -- Correct some long-standing misspellings involving variations of "recur"
6191
6192 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
6193 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6194
6195 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
6196 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6197
6198 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
6199 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
6200
6201 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
6202
6203 -- Might as well keep the comment in sync as well
6204 COMMENT ON TABLE config.rule_recurring_fine IS $$
6205 /*
6206  * Copyright (C) 2005  Georgia Public Library Service 
6207  * Mike Rylander <mrylander@gmail.com>
6208  *
6209  * Circulation Recurring Fine rules
6210  *
6211  * Each circulation is given a recurring fine amount based on one of
6212  * these rules.  The recurrence_interval should not be any shorter
6213  * than the interval between runs of the fine_processor.pl script
6214  * (which is run from CRON), or you could miss fines.
6215  * 
6216  *
6217  * ****
6218  *
6219  * This program is free software; you can redistribute it and/or
6220  * modify it under the terms of the GNU General Public License
6221  * as published by the Free Software Foundation; either version 2
6222  * of the License, or (at your option) any later version.
6223  *
6224  * This program is distributed in the hope that it will be useful,
6225  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6226  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6227  * GNU General Public License for more details.
6228  */
6229 $$;
6230
6231 -- Extend the name change to some related views:
6232
6233 DROP VIEW IF EXISTS reporter.overdue_circs;
6234
6235 CREATE OR REPLACE VIEW reporter.overdue_circs AS
6236 SELECT  *
6237   FROM  action.circulation
6238     WHERE checkin_time is null
6239                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
6240                                 AND due_date < now();
6241
6242 DROP VIEW IF EXISTS stats.fleshed_circulation;
6243
6244 DROP VIEW IF EXISTS stats.fleshed_copy;
6245
6246 CREATE VIEW stats.fleshed_copy AS
6247         SELECT  cp.*,
6248         CAST(cp.create_date AS DATE) AS create_date_day,
6249         CAST(cp.edit_date AS DATE) AS edit_date_day,
6250         DATE_TRUNC('hour', cp.create_date) AS create_date_hour,
6251         DATE_TRUNC('hour', cp.edit_date) AS edit_date_hour,
6252                 cn.label AS call_number_label,
6253                 cn.owning_lib,
6254                 rd.item_lang,
6255                 rd.item_type,
6256                 rd.item_form
6257         FROM    asset.copy cp
6258                 JOIN asset.call_number cn ON (cp.call_number = cn.id)
6259                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
6260
6261 CREATE VIEW stats.fleshed_circulation AS
6262         SELECT  c.*,
6263                 CAST(c.xact_start AS DATE) AS start_date_day,
6264                 CAST(c.xact_finish AS DATE) AS finish_date_day,
6265                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
6266                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
6267                 cp.call_number_label,
6268                 cp.owning_lib,
6269                 cp.item_lang,
6270                 cp.item_type,
6271                 cp.item_form
6272         FROM    action.circulation c
6273                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
6274
6275 -- Drop a view temporarily in order to alter action.all_circulation, upon
6276 -- which it is dependent.  We will recreate the view later.
6277
6278 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
6279
6280 -- You would think that CREATE OR REPLACE would be enough, but in testing
6281 -- PostgreSQL complained about renaming the columns in the view. So we
6282 -- drop the view first.
6283 DROP VIEW IF EXISTS action.all_circulation;
6284
6285 CREATE OR REPLACE VIEW action.all_circulation AS
6286     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6287         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6288         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6289         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6290         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6291         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6292       FROM  action.aged_circulation
6293             UNION ALL
6294     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,
6295         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,
6296         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6297         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6298         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6299         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6300         circ.parent_circ
6301       FROM  action.circulation circ
6302         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6303         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6304         JOIN actor.usr p ON (circ.usr = p.id)
6305         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6306         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
6307
6308 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
6309
6310 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
6311  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
6312    FROM asset."copy" cp
6313    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
6314    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
6315    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
6316   GROUP BY cp.id;
6317
6318 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
6319
6320 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
6321
6322 -- Rebuild dependent views
6323
6324 DROP VIEW IF EXISTS action.billable_circulations;
6325
6326 CREATE OR REPLACE VIEW action.billable_circulations AS
6327     SELECT  *
6328       FROM  action.circulation
6329       WHERE xact_finish IS NULL;
6330
6331 DROP VIEW IF EXISTS action.open_circulation;
6332
6333 CREATE OR REPLACE VIEW action.open_circulation AS
6334     SELECT  *
6335       FROM  action.circulation
6336       WHERE checkin_time IS NULL
6337       ORDER BY due_date;
6338
6339 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
6340 DECLARE
6341 found char := 'N';
6342 BEGIN
6343
6344     -- If there are any renewals for this circulation, don't archive or delete
6345     -- it yet.   We'll do so later, when we archive and delete the renewals.
6346
6347     SELECT 'Y' INTO found
6348     FROM action.circulation
6349     WHERE parent_circ = OLD.id
6350     LIMIT 1;
6351
6352     IF found = 'Y' THEN
6353         RETURN NULL;  -- don't delete
6354         END IF;
6355
6356     -- Archive a copy of the old row to action.aged_circulation
6357
6358     INSERT INTO action.aged_circulation
6359         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6360         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6361         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6362         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6363         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6364         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
6365       SELECT
6366         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6367         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6368         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6369         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6370         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6371         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6372         FROM action.all_circulation WHERE id = OLD.id;
6373
6374     RETURN OLD;
6375 END;
6376 $$ LANGUAGE 'plpgsql';
6377
6378 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
6379
6380 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
6381
6382 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
6383
6384 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$
6385 DECLARE
6386     current_requestor_group    permission.grp_tree%ROWTYPE;
6387     requestor_object    actor.usr%ROWTYPE;
6388     user_object        actor.usr%ROWTYPE;
6389     item_object        asset.copy%ROWTYPE;
6390     item_cn_object        asset.call_number%ROWTYPE;
6391     rec_descriptor        metabib.rec_descriptor%ROWTYPE;
6392     current_mp_weight    FLOAT;
6393     matchpoint_weight    FLOAT;
6394     tmp_weight        FLOAT;
6395     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
6396     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
6397 BEGIN
6398     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6399     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
6400     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6401     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6402     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
6403
6404     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
6405
6406     IF NOT FOUND THEN
6407         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
6408     ELSE
6409         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
6410     END IF;
6411
6412     LOOP 
6413         -- for each potential matchpoint for this ou and group ...
6414         FOR current_mp IN
6415             SELECT    m.*
6416               FROM    config.hold_matrix_matchpoint m
6417               WHERE    m.requestor_grp = current_requestor_group.id AND m.active
6418               ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
6419                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
6420                     CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
6421                     CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
6422                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
6423                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
6424
6425             current_mp_weight := 5.0;
6426
6427             IF current_mp.circ_modifier IS NOT NULL THEN
6428                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6429             END IF;
6430
6431             IF current_mp.marc_type IS NOT NULL THEN
6432                 IF item_object.circ_as_type IS NOT NULL THEN
6433                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6434                 ELSE
6435                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6436                 END IF;
6437             END IF;
6438
6439             IF current_mp.marc_form IS NOT NULL THEN
6440                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6441             END IF;
6442
6443             IF current_mp.marc_vr_format IS NOT NULL THEN
6444                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6445             END IF;
6446
6447             IF current_mp.juvenile_flag IS NOT NULL THEN
6448                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6449             END IF;
6450
6451             IF current_mp.ref_flag IS NOT NULL THEN
6452                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6453             END IF;
6454
6455
6456             -- caclulate the rule match weight
6457             IF current_mp.item_owning_ou IS NOT NULL THEN
6458                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
6459                 current_mp_weight := current_mp_weight - tmp_weight;
6460             END IF; 
6461
6462             IF current_mp.item_circ_ou IS NOT NULL THEN
6463                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
6464                 current_mp_weight := current_mp_weight - tmp_weight;
6465             END IF; 
6466
6467             IF current_mp.pickup_ou IS NOT NULL THEN
6468                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
6469                 current_mp_weight := current_mp_weight - tmp_weight;
6470             END IF; 
6471
6472             IF current_mp.request_ou IS NOT NULL THEN
6473                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
6474                 current_mp_weight := current_mp_weight - tmp_weight;
6475             END IF; 
6476
6477             IF current_mp.user_home_ou IS NOT NULL THEN
6478                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
6479                 current_mp_weight := current_mp_weight - tmp_weight;
6480             END IF; 
6481
6482             -- set the matchpoint if we found the best one
6483             IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
6484                 matchpoint = current_mp;
6485                 matchpoint_weight = current_mp_weight;
6486             END IF;
6487
6488         END LOOP;
6489
6490         EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6491
6492         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
6493     END LOOP;
6494
6495     RETURN matchpoint.id;
6496 END;
6497 $func$ LANGUAGE plpgsql;
6498
6499 CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT, retargetting BOOL ) RETURNS SETOF action.matrix_test_result AS $func$
6500 DECLARE
6501     matchpoint_id        INT;
6502     user_object        actor.usr%ROWTYPE;
6503     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
6504     standing_penalty    config.standing_penalty%ROWTYPE;
6505     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
6506     transit_source        actor.org_unit%ROWTYPE;
6507     item_object        asset.copy%ROWTYPE;
6508     ou_skip              actor.org_unit_setting%ROWTYPE;
6509     result            action.matrix_test_result;
6510     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
6511     hold_count        INT;
6512     hold_transit_prox    INT;
6513     frozen_hold_count    INT;
6514     context_org_list    INT[];
6515     done            BOOL := FALSE;
6516 BEGIN
6517     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6518     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
6519
6520     result.success := TRUE;
6521
6522     -- Fail if we couldn't find a user
6523     IF user_object.id IS NULL THEN
6524         result.fail_part := 'no_user';
6525         result.success := FALSE;
6526         done := TRUE;
6527         RETURN NEXT result;
6528         RETURN;
6529     END IF;
6530
6531     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6532
6533     -- Fail if we couldn't find a copy
6534     IF item_object.id IS NULL THEN
6535         result.fail_part := 'no_item';
6536         result.success := FALSE;
6537         done := TRUE;
6538         RETURN NEXT result;
6539         RETURN;
6540     END IF;
6541
6542     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
6543     result.matchpoint := matchpoint_id;
6544
6545     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
6546
6547     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
6548     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
6549         result.fail_part := 'circ.holds.target_skip_me';
6550         result.success := FALSE;
6551         done := TRUE;
6552         RETURN NEXT result;
6553         RETURN;
6554     END IF;
6555
6556     -- Fail if user is barred
6557     IF user_object.barred IS TRUE THEN
6558         result.fail_part := 'actor.usr.barred';
6559         result.success := FALSE;
6560         done := TRUE;
6561         RETURN NEXT result;
6562         RETURN;
6563     END IF;
6564
6565     -- Fail if we couldn't find any matchpoint (requires a default)
6566     IF matchpoint_id IS NULL THEN
6567         result.fail_part := 'no_matchpoint';
6568         result.success := FALSE;
6569         done := TRUE;
6570         RETURN NEXT result;
6571         RETURN;
6572     END IF;
6573
6574     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
6575
6576     IF hold_test.holdable IS FALSE THEN
6577         result.fail_part := 'config.hold_matrix_test.holdable';
6578         result.success := FALSE;
6579         done := TRUE;
6580         RETURN NEXT result;
6581     END IF;
6582
6583     IF hold_test.transit_range IS NOT NULL THEN
6584         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
6585         IF hold_test.distance_is_from_owner THEN
6586             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;
6587         ELSE
6588             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
6589         END IF;
6590
6591         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
6592
6593         IF NOT FOUND THEN
6594             result.fail_part := 'transit_range';
6595             result.success := FALSE;
6596             done := TRUE;
6597             RETURN NEXT result;
6598         END IF;
6599     END IF;
6600  
6601     IF NOT retargetting THEN
6602         FOR standing_penalty IN
6603             SELECT  DISTINCT csp.*
6604               FROM  actor.usr_standing_penalty usp
6605                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6606               WHERE usr = match_user
6607                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6608                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6609                     AND csp.block_list LIKE '%HOLD%' LOOP
6610     
6611             result.fail_part := standing_penalty.name;
6612             result.success := FALSE;
6613             done := TRUE;
6614             RETURN NEXT result;
6615         END LOOP;
6616     
6617         IF hold_test.stop_blocked_user IS TRUE THEN
6618             FOR standing_penalty IN
6619                 SELECT  DISTINCT csp.*
6620                   FROM  actor.usr_standing_penalty usp
6621                         JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6622                   WHERE usr = match_user
6623                         AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6624                         AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6625                         AND csp.block_list LIKE '%CIRC%' LOOP
6626         
6627                 result.fail_part := standing_penalty.name;
6628                 result.success := FALSE;
6629                 done := TRUE;
6630                 RETURN NEXT result;
6631             END LOOP;
6632         END IF;
6633     
6634         IF hold_test.max_holds IS NOT NULL THEN
6635             SELECT    INTO hold_count COUNT(*)
6636               FROM    action.hold_request
6637               WHERE    usr = match_user
6638                 AND fulfillment_time IS NULL
6639                 AND cancel_time IS NULL
6640                 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
6641     
6642             IF hold_count >= hold_test.max_holds THEN
6643                 result.fail_part := 'config.hold_matrix_test.max_holds';
6644                 result.success := FALSE;
6645                 done := TRUE;
6646                 RETURN NEXT result;
6647             END IF;
6648         END IF;
6649     END IF;
6650
6651     IF item_object.age_protect IS NOT NULL THEN
6652         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
6653
6654         IF item_object.create_date + age_protect_object.age > NOW() THEN
6655             IF hold_test.distance_is_from_owner THEN
6656                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
6657             ELSE
6658                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
6659             END IF;
6660
6661             IF hold_transit_prox > age_protect_object.prox THEN
6662                 result.fail_part := 'config.rule_age_hold_protect.prox';
6663                 result.success := FALSE;
6664                 done := TRUE;
6665                 RETURN NEXT result;
6666             END IF;
6667         END IF;
6668     END IF;
6669
6670     IF NOT done THEN
6671         RETURN NEXT result;
6672     END IF;
6673
6674     RETURN;
6675 END;
6676 $func$ LANGUAGE plpgsql;
6677
6678 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$
6679     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE);
6680 $func$ LANGUAGE SQL;
6681
6682 CREATE OR REPLACE FUNCTION action.hold_retarget_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS SETOF action.matrix_test_result AS $func$
6683     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
6684 $func$ LANGUAGE SQL;
6685
6686 -- New post-delete trigger to propagate deletions to parent(s)
6687
6688 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
6689 BEGIN
6690
6691     -- Having deleted a renewal, we can delete the original circulation (or a previous
6692     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
6693     -- deletion of any prior parents, etc. recursively.
6694
6695     IF OLD.parent_circ IS NOT NULL THEN
6696         DELETE FROM action.circulation
6697         WHERE id = OLD.parent_circ;
6698     END IF;
6699
6700     RETURN OLD;
6701 END;
6702 $$ LANGUAGE 'plpgsql';
6703
6704 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
6705 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
6706
6707 -- This only gets inserted if there are no other id > 100 billing types
6708 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;
6709 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
6710
6711 -- Populate xact_type column in the materialized version of billable_xact_summary
6712
6713 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
6714 BEGIN
6715         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
6716                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
6717         RETURN NEW;
6718 END;
6719 $$ LANGUAGE PLPGSQL;
6720  
6721 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
6722 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
6723  
6724 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
6725 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
6726
6727 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
6728     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;
6729
6730 -- Generate the equivalent of compound subject entries from the existing rows
6731 -- so that we don't have to laboriously reindex them
6732
6733 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
6734 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
6735 --
6736 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
6737 --
6738 --INSERT INTO metabib.subject_field_entry (source, field, value)
6739 --    SELECT source, (
6740 --            SELECT id 
6741 --            FROM config.metabib_field
6742 --            WHERE field_class = 'subject' AND name = 'complete'
6743 --        ), 
6744 --        ARRAY_TO_STRING ( 
6745 --            ARRAY (
6746 --                SELECT value 
6747 --                FROM metabib.subject_field_entry msfe
6748 --                WHERE msfe.source = groupee.source
6749 --                ORDER BY source 
6750 --            ), ' ' 
6751 --        ) AS grouped
6752 --    FROM ( 
6753 --        SELECT source
6754 --        FROM metabib.subject_field_entry
6755 --        GROUP BY source
6756 --    ) AS groupee;
6757
6758 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
6759 DECLARE
6760         prev_billing    money.billing%ROWTYPE;
6761         old_billing     money.billing%ROWTYPE;
6762 BEGIN
6763         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
6764         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
6765
6766         IF OLD.id = old_billing.id THEN
6767                 UPDATE  money.materialized_billable_xact_summary
6768                   SET   last_billing_ts = prev_billing.billing_ts,
6769                         last_billing_note = prev_billing.note,
6770                         last_billing_type = prev_billing.billing_type
6771                   WHERE id = OLD.xact;
6772         END IF;
6773
6774         IF NOT OLD.voided THEN
6775                 UPDATE  money.materialized_billable_xact_summary
6776                   SET   total_owed = total_owed - OLD.amount,
6777                         balance_owed = balance_owed + OLD.amount
6778                   WHERE id = OLD.xact;
6779         END IF;
6780
6781         RETURN OLD;
6782 END;
6783 $$ LANGUAGE PLPGSQL;
6784
6785 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
6786 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
6787
6788 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
6789         use Unicode::Normalize;
6790         use Encode;
6791
6792         # When working with Unicode data, the first step is to decode it to
6793         # a byte string; after that, lowercasing is safe
6794         my $txt = lc(decode_utf8(shift));
6795         my $sf = shift;
6796
6797         $txt = NFD($txt);
6798         $txt =~ s/\pM+//go;     # Remove diacritics
6799
6800         $txt =~ s/\xE6/AE/go;   # Convert ae digraph
6801         $txt =~ s/\x{153}/OE/go;# Convert oe digraph
6802         $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
6803
6804         $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
6805         $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
6806
6807         $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;             # Convert Latin and Greek
6808         $txt =~ tr/\x{2113}\xF0\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LD /;     # Convert Misc
6809         $txt =~ tr/\'\[\]\|//d;                                                 # Remove Misc
6810
6811         if ($sf && $sf =~ /^a/o) {
6812                 my $commapos = index($txt,',');
6813                 if ($commapos > -1) {
6814                         if ($commapos != length($txt) - 1) {
6815                                 my @list = split /,/, $txt;
6816                                 my $first = shift @list;
6817                                 $txt = $first . ',' . join(' ', @list);
6818                         } else {
6819                                 $txt =~ s/,/ /go;
6820                         }
6821                 }
6822         } else {
6823                 $txt =~ s/,/ /go;
6824         }
6825
6826         $txt =~ s/\s+/ /go;     # Compress multiple spaces
6827         $txt =~ s/^\s+//o;      # Remove leading space
6828         $txt =~ s/\s+$//o;      # Remove trailing space
6829
6830         # Encoding the outgoing string is good practice, but not strictly
6831         # necessary in this case because we've stripped everything from it
6832         return encode_utf8($txt);
6833 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
6834
6835 -- Some handy functions, based on existing ones, to provide optional ingest normalization
6836
6837 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6838         SELECT SUBSTRING($1,$2);
6839 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6840
6841 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6842         SELECT SUBSTRING($1,1,$2);
6843 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6844
6845 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
6846         SELECT public.naco_normalize($1,'a');
6847 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6848
6849 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
6850         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
6851 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6852
6853 -- And ... a table in which to register them
6854
6855 CREATE TABLE config.index_normalizer (
6856         id              SERIAL  PRIMARY KEY,
6857         name            TEXT    UNIQUE NOT NULL,
6858         description     TEXT,
6859         func            TEXT    NOT NULL,
6860         param_count     INT     NOT NULL DEFAULT 0
6861 );
6862
6863 CREATE TABLE config.metabib_field_index_norm_map (
6864         id      SERIAL  PRIMARY KEY,
6865         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6866         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6867         params  TEXT,
6868         pos     INT     NOT NULL DEFAULT 0
6869 );
6870
6871 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6872         'NACO Normalize',
6873         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
6874         'naco_normalize',
6875         0
6876 );
6877
6878 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6879         'Normalize date range',
6880         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
6881         'split_date_range',
6882         1
6883 );
6884
6885 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6886         'NACO Normalize -- retain first comma',
6887         '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.',
6888         'naco_normalize_keep_comma',
6889         0
6890 );
6891
6892 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6893         'Strip Diacritics',
6894         'Convert text to NFD form and remove non-spacing combining marks.',
6895         'remove_diacritics',
6896         0
6897 );
6898
6899 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6900         'Up-case',
6901         'Convert text upper case.',
6902         'uppercase',
6903         0
6904 );
6905
6906 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6907         'Down-case',
6908         'Convert text lower case.',
6909         'lowercase',
6910         0
6911 );
6912
6913 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6914         'Extract Dewey-like number',
6915         'Extract a string of numeric characters ther resembles a DDC number.',
6916         'call_number_dewey',
6917         0
6918 );
6919
6920 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6921         'Left truncation',
6922         'Discard the specified number of characters from the left side of the string.',
6923         'left_trunc',
6924         1
6925 );
6926
6927 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6928         'Right truncation',
6929         'Include only the specified number of characters from the left side of the string.',
6930         'right_trunc',
6931         1
6932 );
6933
6934 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6935         'First word',
6936         'Include only the first space-separated word of a string.',
6937         'first_word',
6938         0
6939 );
6940
6941 INSERT INTO config.metabib_field_index_norm_map (field,norm)
6942         SELECT  m.id,
6943                 i.id
6944           FROM  config.metabib_field m,
6945                 config.index_normalizer i
6946           WHERE i.func IN ('naco_normalize','split_date_range');
6947
6948 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
6949 DECLARE
6950     normalizer      RECORD;
6951     value           TEXT := '';
6952 BEGIN
6953
6954     value := NEW.value;
6955
6956     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
6957         FOR normalizer IN
6958             SELECT  n.func AS func,
6959                     n.param_count AS param_count,
6960                     m.params AS params
6961               FROM  config.index_normalizer n
6962                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
6963               WHERE field = NEW.field AND m.pos < 0
6964               ORDER BY m.pos LOOP
6965                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
6966                     quote_literal( value ) ||
6967                     CASE
6968                         WHEN normalizer.param_count > 0
6969                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
6970                             ELSE ''
6971                         END ||
6972                     ')' INTO value;
6973
6974         END LOOP;
6975
6976         NEW.value := value;
6977     END IF;
6978
6979     IF NEW.index_vector = ''::tsvector THEN
6980         RETURN NEW;
6981     END IF;
6982
6983     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
6984         FOR normalizer IN
6985             SELECT  n.func AS func,
6986                     n.param_count AS param_count,
6987                     m.params AS params
6988               FROM  config.index_normalizer n
6989                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
6990               WHERE field = NEW.field AND m.pos >= 0
6991               ORDER BY m.pos LOOP
6992                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
6993                     quote_literal( value ) ||
6994                     CASE
6995                         WHEN normalizer.param_count > 0
6996                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
6997                             ELSE ''
6998                         END ||
6999                     ')' INTO value;
7000
7001         END LOOP;
7002     END IF;
7003
7004     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
7005         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
7006     ELSE
7007         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
7008     END IF;
7009
7010     RETURN NEW;
7011 END;
7012 $$ LANGUAGE PLPGSQL;
7013
7014 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7015
7016 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7017
7018 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7019     SELECT  ARRAY_TO_STRING(
7020                 oils_xpath(
7021                     $1 ||
7022                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
7023                     $2,
7024                     $4
7025                 ),
7026                 $3
7027             );
7028 $func$ LANGUAGE SQL IMMUTABLE;
7029
7030 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
7031     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
7032 $func$ LANGUAGE SQL IMMUTABLE;
7033
7034 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7035     SELECT oils_xpath_string( $1, $2, '', $3 );
7036 $func$ LANGUAGE SQL IMMUTABLE;
7037
7038 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
7039     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
7040 $func$ LANGUAGE SQL IMMUTABLE;
7041
7042 CREATE TYPE metabib.field_entry_template AS (
7043         field_class     TEXT,
7044         field           INT,
7045         source          BIGINT,
7046         value           TEXT
7047 );
7048
7049 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7050   use strict;
7051
7052   use XML::LibXSLT;
7053   use XML::LibXML;
7054
7055   my $doc = shift;
7056   my $xslt = shift;
7057
7058   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7059   # methods of parsing XML documents and stylesheets, in the hopes of broader
7060   # compatibility with distributions
7061   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7062
7063   # Cache the XML parser, if we do not already have one
7064   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7065     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7066
7067   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7068
7069   # Cache the XSLT processor, if we do not already have one
7070   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7071     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7072
7073   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7074     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7075
7076   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7077     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7078
7079   return $stylesheet->output_string(
7080     $stylesheet->transform(
7081       $parser->parse_string($doc)
7082     )
7083   );
7084
7085 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7086
7087 -- Add two columns so that the following function will compile.
7088 -- Eventually the label column will be NOT NULL, but not yet.
7089 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7090 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7091
7092 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7093 DECLARE
7094     bib     biblio.record_entry%ROWTYPE;
7095     idx     config.metabib_field%ROWTYPE;
7096     xfrm        config.xml_transform%ROWTYPE;
7097     prev_xfrm   TEXT;
7098     transformed_xml TEXT;
7099     xml_node    TEXT;
7100     xml_node_list   TEXT[];
7101     facet_text  TEXT;
7102     raw_text    TEXT;
7103     curr_text   TEXT;
7104     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7105     output_row  metabib.field_entry_template%ROWTYPE;
7106 BEGIN
7107
7108     -- Get the record
7109     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7110
7111     -- Loop over the indexing entries
7112     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7113
7114         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7115
7116         -- See if we can skip the XSLT ... it's expensive
7117         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7118             -- Can't skip the transform
7119             IF xfrm.xslt <> '---' THEN
7120                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7121             ELSE
7122                 transformed_xml := bib.marc;
7123             END IF;
7124
7125             prev_xfrm := xfrm.name;
7126         END IF;
7127
7128         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7129
7130         raw_text := NULL;
7131         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7132             CONTINUE WHEN xml_node !~ E'^\\s*<';
7133
7134             curr_text := ARRAY_TO_STRING(
7135                 oils_xpath( '//text()',
7136                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7137                         REGEXP_REPLACE( -- This escapes embeded <s
7138                             xml_node,
7139                             $re$(>[^<]+)(<)([^>]+<)$re$,
7140                             E'\\1&lt;\\3',
7141                             'g'
7142                         ),
7143                         '&(?!amp;)',
7144                         '&amp;',
7145                         'g'
7146                     )
7147                 ),
7148                 ' '
7149             );
7150
7151             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7152
7153             IF raw_text IS NOT NULL THEN
7154                 raw_text := raw_text || joiner;
7155             END IF;
7156
7157             raw_text := COALESCE(raw_text,'') || curr_text;
7158
7159             -- insert raw node text for faceting
7160             IF idx.facet_field THEN
7161
7162                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7163                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7164                 ELSE
7165                     facet_text := curr_text;
7166                 END IF;
7167
7168                 output_row.field_class = idx.field_class;
7169                 output_row.field = -1 * idx.id;
7170                 output_row.source = rid;
7171                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7172
7173                 RETURN NEXT output_row;
7174             END IF;
7175
7176         END LOOP;
7177
7178         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7179
7180         -- insert combined node text for searching
7181         IF idx.search_field THEN
7182             output_row.field_class = idx.field_class;
7183             output_row.field = idx.id;
7184             output_row.source = rid;
7185             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7186
7187             RETURN NEXT output_row;
7188         END IF;
7189
7190     END LOOP;
7191
7192 END;
7193 $func$ LANGUAGE PLPGSQL;
7194
7195 -- default to a space joiner
7196 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7197         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7198 $func$ LANGUAGE SQL;
7199
7200 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7201
7202 use MARC::Record;
7203 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7204
7205 my $xml = shift;
7206 my $r = MARC::Record->new_from_xml( $xml );
7207
7208 return_next( { tag => 'LDR', value => $r->leader } );
7209
7210 for my $f ( $r->fields ) {
7211     if ($f->is_control_field) {
7212         return_next({ tag => $f->tag, value => $f->data });
7213     } else {
7214         for my $s ($f->subfields) {
7215             return_next({
7216                 tag      => $f->tag,
7217                 ind1     => $f->indicator(1),
7218                 ind2     => $f->indicator(2),
7219                 subfield => $s->[0],
7220                 value    => $s->[1]
7221             });
7222
7223             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7224                 my $trim = $f->indicator(2) || 0;
7225                 return_next({
7226                     tag      => 'tnf',
7227                     ind1     => $f->indicator(1),
7228                     ind2     => $f->indicator(2),
7229                     subfield => 'a',
7230                     value    => substr( $s->[1], $trim )
7231                 });
7232             }
7233         }
7234     }
7235 }
7236
7237 return undef;
7238
7239 $func$ LANGUAGE PLPERLU;
7240
7241 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7242 DECLARE
7243     bib biblio.record_entry%ROWTYPE;
7244     output  metabib.full_rec%ROWTYPE;
7245     field   RECORD;
7246 BEGIN
7247     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7248
7249     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7250         output.record := rid;
7251         output.ind1 := field.ind1;
7252         output.ind2 := field.ind2;
7253         output.tag := field.tag;
7254         output.subfield := field.subfield;
7255         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7256             output.value := naco_normalize(field.value, field.subfield);
7257         ELSE
7258             output.value := field.value;
7259         END IF;
7260
7261         CONTINUE WHEN output.value IS NULL;
7262
7263         RETURN NEXT output;
7264     END LOOP;
7265 END;
7266 $func$ LANGUAGE PLPGSQL;
7267
7268 -- functions to create auditor objects
7269
7270 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7271 BEGIN
7272     EXECUTE $$
7273         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7274     $$;
7275         RETURN TRUE;
7276 END;
7277 $creator$ LANGUAGE 'plpgsql';
7278
7279 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7280 BEGIN
7281     EXECUTE $$
7282         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7283             audit_id    BIGINT                          PRIMARY KEY,
7284             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7285             audit_action        TEXT                            NOT NULL,
7286             LIKE $$ || sch || $$.$$ || tbl || $$
7287         );
7288     $$;
7289         RETURN TRUE;
7290 END;
7291 $creator$ LANGUAGE 'plpgsql';
7292
7293 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7294 BEGIN
7295     EXECUTE $$
7296         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7297         RETURNS TRIGGER AS $func$
7298         BEGIN
7299             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7300                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7301                     now(),
7302                     SUBSTR(TG_OP,1,1),
7303                     OLD.*;
7304             RETURN NULL;
7305         END;
7306         $func$ LANGUAGE 'plpgsql';
7307     $$;
7308         RETURN TRUE;
7309 END;
7310 $creator$ LANGUAGE 'plpgsql';
7311
7312 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7313 BEGIN
7314     EXECUTE $$
7315         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7316             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7317             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7318     $$;
7319         RETURN TRUE;
7320 END;
7321 $creator$ LANGUAGE 'plpgsql';
7322
7323 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7324 BEGIN
7325     EXECUTE $$
7326         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7327             SELECT      -1, now() as audit_time, '-' as audit_action, *
7328               FROM      $$ || sch || $$.$$ || tbl || $$
7329                 UNION ALL
7330             SELECT      *
7331               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7332     $$;
7333         RETURN TRUE;
7334 END;
7335 $creator$ LANGUAGE 'plpgsql';
7336
7337 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7338
7339 -- The main event
7340
7341 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7342 BEGIN
7343     PERFORM auditor.create_auditor_seq(sch, tbl);
7344     PERFORM auditor.create_auditor_history(sch, tbl);
7345     PERFORM auditor.create_auditor_func(sch, tbl);
7346     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7347     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7348     RETURN TRUE;
7349 END;
7350 $creator$ LANGUAGE 'plpgsql';
7351
7352 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7353
7354 ALTER TABLE action.hold_request
7355 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7356
7357 ALTER TABLE action.hold_request
7358 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7359
7360 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7361
7362 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7363
7364 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7365
7366 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7367
7368 -- Add claims_never_checked_out_count to actor.usr, related history
7369
7370 ALTER TABLE actor.usr ADD COLUMN
7371         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7372
7373 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7374         claims_never_checked_out_count INT;
7375
7376 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7377
7378 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7379
7380 -----------
7381
7382 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7383 BEGIN
7384         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7385                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7386                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7387                 END IF;
7388                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7389                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7390                 END IF;
7391                 IF NEW.stop_fines = 'LOST' THEN
7392                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7393                 END IF;
7394         END IF;
7395         RETURN NEW;
7396 END;
7397 $$ LANGUAGE 'plpgsql';
7398
7399 -- Create new table acq.fund_allocation_percent
7400 -- Populate it from acq.fund_allocation
7401 -- Convert all percentages to amounts in acq.fund_allocation
7402
7403 CREATE TABLE acq.fund_allocation_percent
7404 (
7405     id                   SERIAL            PRIMARY KEY,
7406     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7407                                                DEFERRABLE INITIALLY DEFERRED,
7408     org                  INT               NOT NULL REFERENCES actor.org_unit
7409                                                DEFERRABLE INITIALLY DEFERRED,
7410     fund_code            TEXT,
7411     percent              NUMERIC           NOT NULL,
7412     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7413                                                DEFERRABLE INITIALLY DEFERRED,
7414     note                 TEXT,
7415     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7416     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7417     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7418 );
7419
7420 -- Trigger function to validate combination of org_unit and fund_code
7421
7422 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7423 RETURNS TRIGGER AS $$
7424 --
7425 DECLARE
7426 --
7427 dummy int := 0;
7428 --
7429 BEGIN
7430     SELECT
7431         1
7432     INTO
7433         dummy
7434     FROM
7435         acq.fund
7436     WHERE
7437         org = NEW.org
7438         AND code = NEW.fund_code
7439         LIMIT 1;
7440     --
7441     IF dummy = 1 then
7442         RETURN NEW;
7443     ELSE
7444         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7445     END IF;
7446 END;
7447 $$ LANGUAGE plpgsql;
7448
7449 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7450     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7451     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7452
7453 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7454 RETURNS TRIGGER AS $$
7455 DECLARE
7456 --
7457 total_percent numeric;
7458 --
7459 BEGIN
7460     SELECT
7461         sum( percent )
7462     INTO
7463         total_percent
7464     FROM
7465         acq.fund_allocation_percent AS fap
7466     WHERE
7467         fap.funding_source = NEW.funding_source;
7468     --
7469     IF total_percent > 100 THEN
7470         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7471             NEW.funding_source;
7472     ELSE
7473         RETURN NEW;
7474     END IF;
7475 END;
7476 $$ LANGUAGE plpgsql;
7477
7478 CREATE TRIGGER acqfap_limit_100_trig
7479     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7480     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7481
7482 -- Populate new table from acq.fund_allocation
7483
7484 INSERT INTO acq.fund_allocation_percent
7485 (
7486     funding_source,
7487     org,
7488     fund_code,
7489     percent,
7490     allocator,
7491     note,
7492     create_time
7493 )
7494     SELECT
7495         fa.funding_source,
7496         fund.org,
7497         fund.code,
7498         fa.percent,
7499         fa.allocator,
7500         fa.note,
7501         fa.create_time
7502     FROM
7503         acq.fund_allocation AS fa
7504             INNER JOIN acq.fund AS fund
7505                 ON ( fa.fund = fund.id )
7506     WHERE
7507         fa.percent is not null
7508     ORDER BY
7509         fund.org;
7510
7511 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7512
7513 -- Algorithm to apply to each funding source:
7514
7515 -- 1. Add up the credits.
7516 -- 2. Add up the percentages.
7517 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7518 --    fractional cents from the result.  This is the total amount to be allocated.
7519 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7520 --    fractional cents to get a preliminary amount.
7521 -- 5. Add up the preliminary amounts for all the allocations.
7522 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7523 --    number of residual cents (resulting from having dropped fractional cents) that
7524 --    must be distributed across the funds in order to make the total of the amounts
7525 --    match the total allocation.
7526 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7527 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7528 --    for each successive fund, until all the residual cents have been exhausted.
7529
7530 -- Result: the sum of the individual allocations now equals the total to be allocated,
7531 -- to the penny.  The individual amounts match the percentages as closely as possible,
7532 -- given the constraint that the total must match.
7533
7534 CREATE OR REPLACE FUNCTION acq.apply_percents()
7535 RETURNS VOID AS $$
7536 declare
7537 --
7538 tot              RECORD;
7539 fund             RECORD;
7540 tot_cents        INTEGER;
7541 src              INTEGER;
7542 id               INTEGER[];
7543 curr_id          INTEGER;
7544 pennies          NUMERIC[];
7545 curr_amount      NUMERIC;
7546 i                INTEGER;
7547 total_of_floors  INTEGER;
7548 total_percent    NUMERIC;
7549 total_allocation INTEGER;
7550 residue          INTEGER;
7551 --
7552 begin
7553         RAISE NOTICE 'Applying percents';
7554         FOR tot IN
7555                 SELECT
7556                         fsrc.funding_source,
7557                         sum( fsrc.amount ) AS total
7558                 FROM
7559                         acq.funding_source_credit AS fsrc
7560                 WHERE fsrc.funding_source IN
7561                         ( SELECT DISTINCT fa.funding_source
7562                           FROM acq.fund_allocation AS fa
7563                           WHERE fa.percent IS NOT NULL )
7564                 GROUP BY
7565                         fsrc.funding_source
7566         LOOP
7567                 tot_cents = floor( tot.total * 100 );
7568                 src = tot.funding_source;
7569                 RAISE NOTICE 'Funding source % total %',
7570                         src, tot_cents;
7571                 i := 0;
7572                 total_of_floors := 0;
7573                 total_percent := 0;
7574                 --
7575                 FOR fund in
7576                         SELECT
7577                                 fa.id,
7578                                 fa.percent,
7579                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7580                         FROM
7581                                 acq.fund_allocation AS fa
7582                         WHERE
7583                                 fa.funding_source = src
7584                                 AND fa.percent IS NOT NULL
7585                         ORDER BY
7586                                 mod( fa.percent * tot_cents / 100, 1 ),
7587                                 fa.fund,
7588                                 fa.id
7589                 LOOP
7590                         RAISE NOTICE '   %: %',
7591                                 fund.id,
7592                                 fund.floor_pennies;
7593                         i := i + 1;
7594                         id[i] = fund.id;
7595                         pennies[i] = fund.floor_pennies;
7596                         total_percent := total_percent + fund.percent;
7597                         total_of_floors := total_of_floors + pennies[i];
7598                 END LOOP;
7599                 total_allocation := floor( total_percent * tot_cents /100 );
7600                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7601                 residue := total_allocation - total_of_floors;
7602                 RAISE NOTICE 'Residue: %', residue;
7603                 --
7604                 -- Post the calculated amounts, revising as needed to
7605                 -- distribute the rounding error
7606                 --
7607                 WHILE i > 0 LOOP
7608                         IF residue > 0 THEN
7609                                 pennies[i] = pennies[i] + 1;
7610                                 residue := residue - 1;
7611                         END IF;
7612                         --
7613                         -- Post amount
7614                         --
7615                         curr_id     := id[i];
7616                         curr_amount := trunc( pennies[i] / 100, 2 );
7617                         --
7618                         UPDATE
7619                                 acq.fund_allocation AS fa
7620                         SET
7621                                 amount = curr_amount,
7622                                 percent = NULL
7623                         WHERE
7624                                 fa.id = curr_id;
7625                         --
7626                         RAISE NOTICE '   ID % and amount %',
7627                                 curr_id,
7628                                 curr_amount;
7629                         i = i - 1;
7630                 END LOOP;
7631         END LOOP;
7632 end;
7633 $$ LANGUAGE 'plpgsql';
7634
7635 -- Run the temporary function
7636
7637 select * from acq.apply_percents();
7638
7639 -- Drop the temporary function now that we're done with it
7640
7641 DROP FUNCTION IF EXISTS acq.apply_percents();
7642
7643 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7644
7645 -- If the following step fails, it's probably because there are still some non-null percent values in
7646 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7647 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7648 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7649 -- slipped in afterwards.
7650
7651 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7652 -- procedure acq.apply_percents() as defined above.
7653
7654 ALTER TABLE acq.fund_allocation
7655 ALTER COLUMN amount SET NOT NULL;
7656
7657 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7658     SELECT  fund,
7659             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7660     FROM acq.fund_allocation a
7661          JOIN acq.fund f ON (a.fund = f.id)
7662          JOIN acq.funding_source s ON (a.funding_source = s.id)
7663     GROUP BY 1;
7664
7665 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7666     SELECT  funding_source,
7667             SUM(a.amount)::NUMERIC(100,2) AS amount
7668     FROM  acq.fund_allocation a
7669     GROUP BY 1;
7670
7671 ALTER TABLE acq.fund_allocation
7672 DROP COLUMN percent;
7673
7674 CREATE TABLE asset.copy_location_order
7675 (
7676         id              SERIAL           PRIMARY KEY,
7677         location        INT              NOT NULL
7678                                              REFERENCES asset.copy_location
7679                                              ON DELETE CASCADE
7680                                              DEFERRABLE INITIALLY DEFERRED,
7681         org             INT              NOT NULL
7682                                              REFERENCES actor.org_unit
7683                                              ON DELETE CASCADE
7684                                              DEFERRABLE INITIALLY DEFERRED,
7685         position        INT              NOT NULL DEFAULT 0,
7686         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7687 );
7688
7689 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7690
7691 -- If you ran this before its most recent incarnation:
7692 -- delete from config.upgrade_log where version = '0328';
7693 -- alter table money.credit_card_payment drop column cc_name;
7694
7695 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7696 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7697
7698 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$
7699 DECLARE
7700     current_group    permission.grp_tree%ROWTYPE;
7701     user_object    actor.usr%ROWTYPE;
7702     item_object    asset.copy%ROWTYPE;
7703     cn_object    asset.call_number%ROWTYPE;
7704     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7705     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7706     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7707 BEGIN
7708     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7709     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7710     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7711     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7712     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7713
7714     LOOP 
7715         -- for each potential matchpoint for this ou and group ...
7716         FOR current_mp IN
7717             SELECT  m.*
7718               FROM  config.circ_matrix_matchpoint m
7719                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
7720                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
7721               WHERE m.grp = current_group.id
7722                     AND m.active
7723                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
7724                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
7725               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
7726                     CASE WHEN m.copy_owning_lib IS NOT NULL
7727                         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 )
7728                         ELSE 0
7729                     END +
7730                     CASE WHEN m.copy_circ_lib IS NOT NULL
7731                         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 )
7732                         ELSE 0
7733                     END +
7734                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
7735                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
7736                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
7737                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
7738                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
7739                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
7740                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
7741                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
7742                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
7743
7744             IF current_mp.is_renewal IS NOT NULL THEN
7745                 CONTINUE WHEN current_mp.is_renewal <> renewal;
7746             END IF;
7747
7748             IF current_mp.circ_modifier IS NOT NULL THEN
7749                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
7750             END IF;
7751
7752             IF current_mp.marc_type IS NOT NULL THEN
7753                 IF item_object.circ_as_type IS NOT NULL THEN
7754                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
7755                 ELSE
7756                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
7757                 END IF;
7758             END IF;
7759
7760             IF current_mp.marc_form IS NOT NULL THEN
7761                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
7762             END IF;
7763
7764             IF current_mp.marc_vr_format IS NOT NULL THEN
7765                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
7766             END IF;
7767
7768             IF current_mp.ref_flag IS NOT NULL THEN
7769                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
7770             END IF;
7771
7772             IF current_mp.juvenile_flag IS NOT NULL THEN
7773                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
7774             END IF;
7775
7776             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
7777                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
7778             END IF;
7779
7780             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
7781                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
7782             END IF;
7783
7784
7785             -- everything was undefined or matched
7786             matchpoint = current_mp;
7787
7788             EXIT WHEN matchpoint.id IS NOT NULL;
7789         END LOOP;
7790
7791         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
7792
7793         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
7794     END LOOP;
7795
7796     RETURN matchpoint;
7797 END;
7798 $func$ LANGUAGE plpgsql;
7799
7800 CREATE TYPE action.hold_stats AS (
7801     hold_count              INT,
7802     copy_count              INT,
7803     available_count         INT,
7804     total_copy_ratio        FLOAT,
7805     available_copy_ratio    FLOAT
7806 );
7807
7808 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
7809 DECLARE
7810     output          action.hold_stats%ROWTYPE;
7811     hold_count      INT := 0;
7812     copy_count      INT := 0;
7813     available_count INT := 0;
7814     hold_map_data   RECORD;
7815 BEGIN
7816
7817     output.hold_count := 0;
7818     output.copy_count := 0;
7819     output.available_count := 0;
7820
7821     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
7822       FROM  action.hold_copy_map m
7823             JOIN action.hold_request h ON (m.hold = h.id)
7824       WHERE m.target_copy = copy_id
7825             AND NOT h.frozen;
7826
7827     output.hold_count := hold_count;
7828
7829     IF output.hold_count > 0 THEN
7830         FOR hold_map_data IN
7831             SELECT  DISTINCT m.target_copy,
7832                     acp.status
7833               FROM  action.hold_copy_map m
7834                     JOIN asset.copy acp ON (m.target_copy = acp.id)
7835                     JOIN action.hold_request h ON (m.hold = h.id)
7836               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
7837         LOOP
7838             output.copy_count := output.copy_count + 1;
7839             IF hold_map_data.status IN (0,7,12) THEN
7840                 output.available_count := output.available_count + 1;
7841             END IF;
7842         END LOOP;
7843         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
7844         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
7845
7846     END IF;
7847
7848     RETURN output;
7849
7850 END;
7851 $func$ LANGUAGE PLPGSQL;
7852
7853 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
7854 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
7855
7856 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
7857
7858 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7859 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7860
7861 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
7862     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
7863     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
7864     copy_owning_lib
7865 );
7866
7867 -- Return the correct fail_part when the item can't be found
7868 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$
7869 DECLARE
7870     user_object        actor.usr%ROWTYPE;
7871     standing_penalty    config.standing_penalty%ROWTYPE;
7872     item_object        asset.copy%ROWTYPE;
7873     item_status_object    config.copy_status%ROWTYPE;
7874     item_location_object    asset.copy_location%ROWTYPE;
7875     result            action.matrix_test_result;
7876     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
7877     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
7878     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
7879     hold_ratio          action.hold_stats%ROWTYPE;
7880     penalty_type         TEXT;
7881     tmp_grp         INT;
7882     items_out        INT;
7883     context_org_list        INT[];
7884     done            BOOL := FALSE;
7885 BEGIN
7886     result.success := TRUE;
7887
7888     -- Fail if the user is BARRED
7889     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7890
7891     -- Fail if we couldn't find the user 
7892     IF user_object.id IS NULL THEN
7893         result.fail_part := 'no_user';
7894         result.success := FALSE;
7895         done := TRUE;
7896         RETURN NEXT result;
7897         RETURN;
7898     END IF;
7899
7900     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7901
7902     -- Fail if we couldn't find the item 
7903     IF item_object.id IS NULL THEN
7904         result.fail_part := 'no_item';
7905         result.success := FALSE;
7906         done := TRUE;
7907         RETURN NEXT result;
7908         RETURN;
7909     END IF;
7910
7911     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
7912     result.matchpoint := circ_test.id;
7913
7914     -- Fail if we couldn't find a matchpoint
7915     IF result.matchpoint IS NULL THEN
7916         result.fail_part := 'no_matchpoint';
7917         result.success := FALSE;
7918         done := TRUE;
7919         RETURN NEXT result;
7920     END IF;
7921
7922     IF user_object.barred IS TRUE THEN
7923         result.fail_part := 'actor.usr.barred';
7924         result.success := FALSE;
7925         done := TRUE;
7926         RETURN NEXT result;
7927     END IF;
7928
7929     -- Fail if the item can't circulate
7930     IF item_object.circulate IS FALSE THEN
7931         result.fail_part := 'asset.copy.circulate';
7932         result.success := FALSE;
7933         done := TRUE;
7934         RETURN NEXT result;
7935     END IF;
7936
7937     -- Fail if the item isn't in a circulateable status on a non-renewal
7938     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
7939         result.fail_part := 'asset.copy.status';
7940         result.success := FALSE;
7941         done := TRUE;
7942         RETURN NEXT result;
7943     ELSIF renewal AND item_object.status <> 1 THEN
7944         result.fail_part := 'asset.copy.status';
7945         result.success := FALSE;
7946         done := TRUE;
7947         RETURN NEXT result;
7948     END IF;
7949
7950     -- Fail if the item can't circulate because of the shelving location
7951     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
7952     IF item_location_object.circulate IS FALSE THEN
7953         result.fail_part := 'asset.copy_location.circulate';
7954         result.success := FALSE;
7955         done := TRUE;
7956         RETURN NEXT result;
7957     END IF;
7958
7959     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
7960
7961     -- Fail if the test is set to hard non-circulating
7962     IF circ_test.circulate IS FALSE THEN
7963         result.fail_part := 'config.circ_matrix_test.circulate';
7964         result.success := FALSE;
7965         done := TRUE;
7966         RETURN NEXT result;
7967     END IF;
7968
7969     -- Fail if the total copy-hold ratio is too low
7970     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
7971         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
7972         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
7973             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
7974             result.success := FALSE;
7975             done := TRUE;
7976             RETURN NEXT result;
7977         END IF;
7978     END IF;
7979
7980     -- Fail if the available copy-hold ratio is too low
7981     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
7982         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
7983         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
7984             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
7985             result.success := FALSE;
7986             done := TRUE;
7987             RETURN NEXT result;
7988         END IF;
7989     END IF;
7990
7991     IF renewal THEN
7992         penalty_type = '%RENEW%';
7993     ELSE
7994         penalty_type = '%CIRC%';
7995     END IF;
7996
7997     FOR standing_penalty IN
7998         SELECT  DISTINCT csp.*
7999           FROM  actor.usr_standing_penalty usp
8000                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8001           WHERE usr = match_user
8002                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
8003                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8004                 AND csp.block_list LIKE penalty_type LOOP
8005
8006         result.fail_part := standing_penalty.name;
8007         result.success := FALSE;
8008         done := TRUE;
8009         RETURN NEXT result;
8010     END LOOP;
8011
8012     -- Fail if the user has too many items with specific circ_modifiers checked out
8013     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
8014         SELECT  INTO items_out COUNT(*)
8015           FROM  action.circulation circ
8016             JOIN asset.copy cp ON (cp.id = circ.target_copy)
8017           WHERE circ.usr = match_user
8018                AND circ.circ_lib IN ( SELECT * FROM explode_array(context_org_list) )
8019             AND circ.checkin_time IS NULL
8020             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8021             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);
8022         IF items_out >= out_by_circ_mod.items_out THEN
8023             result.fail_part := 'config.circ_matrix_circ_mod_test';
8024             result.success := FALSE;
8025             done := TRUE;
8026             RETURN NEXT result;
8027         END IF;
8028     END LOOP;
8029
8030     -- If we passed everything, return the successful matchpoint id
8031     IF NOT done THEN
8032         RETURN NEXT result;
8033     END IF;
8034
8035     RETURN;
8036 END;
8037 $func$ LANGUAGE plpgsql;
8038
8039 CREATE TABLE config.remote_account (
8040     id          SERIAL  PRIMARY KEY,
8041     label       TEXT    NOT NULL,
8042     host        TEXT    NOT NULL,   -- name or IP, :port optional
8043     username    TEXT,               -- optional, since we could default to $USER
8044     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
8045     account     TEXT,               -- aka profile or FTP "account" command
8046     path        TEXT,               -- aka directory
8047     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8048     last_activity TIMESTAMP WITH TIME ZONE
8049 );
8050
8051 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8052     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8053     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8054         vendcode    TEXT,
8055         vendacct    TEXT
8056
8057 ) INHERITS (config.remote_account);
8058
8059 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8060
8061 CREATE TABLE acq.claim_type (
8062         id             SERIAL           PRIMARY KEY,
8063         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8064                                                  DEFERRABLE INITIALLY DEFERRED,
8065         code           TEXT             NOT NULL,
8066         description    TEXT             NOT NULL,
8067         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8068 );
8069
8070 CREATE TABLE acq.claim (
8071         id             SERIAL           PRIMARY KEY,
8072         type           INT              NOT NULL REFERENCES acq.claim_type
8073                                                  DEFERRABLE INITIALLY DEFERRED,
8074         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8075                                                  DEFERRABLE INITIALLY DEFERRED
8076 );
8077
8078 CREATE TABLE acq.claim_policy (
8079         id              SERIAL       PRIMARY KEY,
8080         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8081                                      DEFERRABLE INITIALLY DEFERRED,
8082         name            TEXT         NOT NULL,
8083         description     TEXT         NOT NULL,
8084         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8085 );
8086
8087 -- Add a san column for EDI. 
8088 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8089
8090 ALTER TABLE acq.provider ADD COLUMN san INT;
8091
8092 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8093
8094 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8095 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8096
8097 ALTER TABLE acq.provider
8098         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8099
8100 ALTER TABLE acq.provider
8101         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8102
8103 ALTER TABLE acq.provider
8104         ADD COLUMN url TEXT;
8105
8106 ALTER TABLE acq.provider
8107         ADD COLUMN email TEXT;
8108
8109 ALTER TABLE acq.provider
8110         ADD COLUMN phone TEXT;
8111
8112 ALTER TABLE acq.provider
8113         ADD COLUMN fax_phone TEXT;
8114
8115 ALTER TABLE acq.provider
8116         ADD COLUMN default_claim_policy INT
8117                 REFERENCES acq.claim_policy
8118                 DEFERRABLE INITIALLY DEFERRED;
8119
8120 ALTER TABLE action.transit_copy
8121 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8122                                                          DEFERRABLE INITIALLY DEFERRED;
8123
8124 DROP SCHEMA IF EXISTS booking CASCADE;
8125
8126 CREATE SCHEMA booking;
8127
8128 CREATE TABLE booking.resource_type (
8129         id             SERIAL          PRIMARY KEY,
8130         name           TEXT            NOT NULL,
8131         fine_interval  INTERVAL,
8132         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
8133         owner          INT             NOT NULL
8134                                        REFERENCES actor.org_unit( id )
8135                                        DEFERRABLE INITIALLY DEFERRED,
8136         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
8137         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
8138     record         BIGINT          REFERENCES biblio.record_entry (id)
8139                                        DEFERRABLE INITIALLY DEFERRED,
8140     max_fine       NUMERIC(8,2),
8141     elbow_room     INTERVAL,
8142     CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
8143 );
8144
8145 CREATE TABLE booking.resource (
8146         id             SERIAL           PRIMARY KEY,
8147         owner          INT              NOT NULL
8148                                         REFERENCES actor.org_unit(id)
8149                                         DEFERRABLE INITIALLY DEFERRED,
8150         type           INT              NOT NULL
8151                                         REFERENCES booking.resource_type(id)
8152                                         DEFERRABLE INITIALLY DEFERRED,
8153         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
8154         barcode        TEXT             NOT NULL,
8155         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
8156         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8157         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8158         CONSTRAINT br_unique UNIQUE (owner, barcode)
8159 );
8160
8161 -- For non-catalog items: hijack barcode for name/description
8162
8163 CREATE TABLE booking.resource_attr (
8164         id              SERIAL          PRIMARY KEY,
8165         owner           INT             NOT NULL
8166                                         REFERENCES actor.org_unit(id)
8167                                         DEFERRABLE INITIALLY DEFERRED,
8168         name            TEXT            NOT NULL,
8169         resource_type   INT             NOT NULL
8170                                         REFERENCES booking.resource_type(id)
8171                                         ON DELETE CASCADE
8172                                         DEFERRABLE INITIALLY DEFERRED,
8173         required        BOOLEAN         NOT NULL DEFAULT FALSE,
8174         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
8175 );
8176
8177 CREATE TABLE booking.resource_attr_value (
8178         id               SERIAL         PRIMARY KEY,
8179         owner            INT            NOT NULL
8180                                         REFERENCES actor.org_unit(id)
8181                                         DEFERRABLE INITIALLY DEFERRED,
8182         attr             INT            NOT NULL
8183                                         REFERENCES booking.resource_attr(id)
8184                                         DEFERRABLE INITIALLY DEFERRED,
8185         valid_value      TEXT           NOT NULL,
8186         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
8187 );
8188
8189 CREATE TABLE booking.resource_attr_map (
8190         id               SERIAL         PRIMARY KEY,
8191         resource         INT            NOT NULL
8192                                         REFERENCES booking.resource(id)
8193                                         ON DELETE CASCADE
8194                                         DEFERRABLE INITIALLY DEFERRED,
8195         resource_attr    INT            NOT NULL
8196                                         REFERENCES booking.resource_attr(id)
8197                                         ON DELETE CASCADE
8198                                         DEFERRABLE INITIALLY DEFERRED,
8199         value            INT            NOT NULL
8200                                         REFERENCES booking.resource_attr_value(id)
8201                                         DEFERRABLE INITIALLY DEFERRED,
8202         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
8203 );
8204
8205 CREATE TABLE booking.reservation (
8206         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
8207         start_time       TIMESTAMPTZ,
8208         end_time         TIMESTAMPTZ,
8209         capture_time     TIMESTAMPTZ,
8210         cancel_time      TIMESTAMPTZ,
8211         pickup_time      TIMESTAMPTZ,
8212         return_time      TIMESTAMPTZ,
8213         booking_interval INTERVAL,
8214         fine_interval    INTERVAL,
8215         fine_amount      DECIMAL(8,2),
8216         target_resource_type  INT       NOT NULL
8217                                         REFERENCES booking.resource_type(id)
8218                                         ON DELETE CASCADE
8219                                         DEFERRABLE INITIALLY DEFERRED,
8220         target_resource  INT            REFERENCES booking.resource(id)
8221                                         ON DELETE CASCADE
8222                                         DEFERRABLE INITIALLY DEFERRED,
8223         current_resource INT            REFERENCES booking.resource(id)
8224                                         ON DELETE CASCADE
8225                                         DEFERRABLE INITIALLY DEFERRED,
8226         request_lib      INT            NOT NULL
8227                                         REFERENCES actor.org_unit(id)
8228                                         DEFERRABLE INITIALLY DEFERRED,
8229         pickup_lib       INT            REFERENCES actor.org_unit(id)
8230                                         DEFERRABLE INITIALLY DEFERRED,
8231         capture_staff    INT            REFERENCES actor.usr(id)
8232                                         DEFERRABLE INITIALLY DEFERRED,
8233     max_fine         NUMERIC(8,2)
8234 ) INHERITS (money.billable_xact);
8235
8236 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
8237
8238 ALTER TABLE booking.reservation
8239         ADD CONSTRAINT booking_reservation_usr_fkey
8240         FOREIGN KEY (usr) REFERENCES actor.usr (id)
8241         DEFERRABLE INITIALLY DEFERRED;
8242
8243 CREATE TABLE booking.reservation_attr_value_map (
8244         id               SERIAL         PRIMARY KEY,
8245         reservation      INT            NOT NULL
8246                                         REFERENCES booking.reservation(id)
8247                                         ON DELETE CASCADE
8248                                         DEFERRABLE INITIALLY DEFERRED,
8249         attr_value       INT            NOT NULL
8250                                         REFERENCES booking.resource_attr_value(id)
8251                                         ON DELETE CASCADE
8252                                         DEFERRABLE INITIALLY DEFERRED,
8253         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
8254 );
8255
8256 -- represents a circ chain summary
8257 CREATE TYPE action.circ_chain_summary AS (
8258     num_circs INTEGER,
8259     start_time TIMESTAMP WITH TIME ZONE,
8260     checkout_workstation TEXT,
8261     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8262     last_stop_fines TEXT,
8263     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8264     last_renewal_workstation TEXT, -- NULL if no renewals
8265     last_checkin_workstation TEXT,
8266     last_checkin_time TIMESTAMP WITH TIME ZONE,
8267     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8268 );
8269
8270 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8271 DECLARE
8272     tmp_circ action.circulation%ROWTYPE;
8273     circ_0 action.circulation%ROWTYPE;
8274 BEGIN
8275
8276     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8277
8278     IF tmp_circ IS NULL THEN
8279         RETURN NEXT tmp_circ;
8280     END IF;
8281     circ_0 := tmp_circ;
8282
8283     -- find the front of the chain
8284     WHILE TRUE LOOP
8285         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8286         IF tmp_circ IS NULL THEN
8287             EXIT;
8288         END IF;
8289         circ_0 := tmp_circ;
8290     END LOOP;
8291
8292     -- now send the circs to the caller, oldest to newest
8293     tmp_circ := circ_0;
8294     WHILE TRUE LOOP
8295         IF tmp_circ IS NULL THEN
8296             EXIT;
8297         END IF;
8298         RETURN NEXT tmp_circ;
8299         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8300     END LOOP;
8301
8302 END;
8303 $$ LANGUAGE 'plpgsql';
8304
8305 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8306
8307 DECLARE
8308
8309     -- first circ in the chain
8310     circ_0 action.circulation%ROWTYPE;
8311
8312     -- last circ in the chain
8313     circ_n action.circulation%ROWTYPE;
8314
8315     -- circ chain under construction
8316     chain action.circ_chain_summary;
8317     tmp_circ action.circulation%ROWTYPE;
8318
8319 BEGIN
8320     
8321     chain.num_circs := 0;
8322     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8323
8324         IF chain.num_circs = 0 THEN
8325             circ_0 := tmp_circ;
8326         END IF;
8327
8328         chain.num_circs := chain.num_circs + 1;
8329         circ_n := tmp_circ;
8330     END LOOP;
8331
8332     chain.start_time := circ_0.xact_start;
8333     chain.last_stop_fines := circ_n.stop_fines;
8334     chain.last_stop_fines_time := circ_n.stop_fines_time;
8335     chain.last_checkin_time := circ_n.checkin_time;
8336     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8337     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8338     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8339
8340     IF chain.num_circs > 1 THEN
8341         chain.last_renewal_time := circ_n.xact_start;
8342         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8343     END IF;
8344
8345     RETURN chain;
8346
8347 END;
8348 $$ LANGUAGE 'plpgsql';
8349
8350 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8351 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8352 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8353
8354 ALTER TABLE config.standing_penalty
8355         ADD COLUMN org_depth   INTEGER;
8356
8357 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8358 DECLARE
8359     user_object         actor.usr%ROWTYPE;
8360     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8361     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8362     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8363     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8364     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8365     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8366     tmp_grp             INT;
8367     items_overdue       INT;
8368     items_out           INT;
8369     context_org_list    INT[];
8370     current_fines        NUMERIC(8,2) := 0.0;
8371     tmp_fines            NUMERIC(8,2);
8372     tmp_groc            RECORD;
8373     tmp_circ            RECORD;
8374     tmp_org             actor.org_unit%ROWTYPE;
8375     tmp_penalty         config.standing_penalty%ROWTYPE;
8376     tmp_depth           INTEGER;
8377 BEGIN
8378     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8379
8380     -- Max fines
8381     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8382
8383     -- Fail if the user has a high fine balance
8384     LOOP
8385         tmp_grp := user_object.profile;
8386         LOOP
8387             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8388
8389             IF max_fines.threshold IS NULL THEN
8390                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8391             ELSE
8392                 EXIT;
8393             END IF;
8394
8395             IF tmp_grp IS NULL THEN
8396                 EXIT;
8397             END IF;
8398         END LOOP;
8399
8400         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8401             EXIT;
8402         END IF;
8403
8404         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8405
8406     END LOOP;
8407
8408     IF max_fines.threshold IS NOT NULL THEN
8409
8410         FOR existing_sp_row IN
8411                 SELECT  *
8412                   FROM  actor.usr_standing_penalty
8413                   WHERE usr = match_user
8414                         AND org_unit = max_fines.org_unit
8415                         AND (stop_date IS NULL or stop_date > NOW())
8416                         AND standing_penalty = 1
8417                 LOOP
8418             RETURN NEXT existing_sp_row;
8419         END LOOP;
8420
8421         SELECT  SUM(f.balance_owed) INTO current_fines
8422           FROM  money.materialized_billable_xact_summary f
8423                 JOIN (
8424                     SELECT  r.id
8425                       FROM  booking.reservation r
8426                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8427                       WHERE usr = match_user
8428                             AND xact_finish IS NULL
8429                                 UNION ALL
8430                     SELECT  g.id
8431                       FROM  money.grocery g
8432                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8433                       WHERE usr = match_user
8434                             AND xact_finish IS NULL
8435                                 UNION ALL
8436                     SELECT  circ.id
8437                       FROM  action.circulation circ
8438                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8439                       WHERE usr = match_user
8440                             AND xact_finish IS NULL ) l USING (id);
8441
8442         IF current_fines >= max_fines.threshold THEN
8443             new_sp_row.usr := match_user;
8444             new_sp_row.org_unit := max_fines.org_unit;
8445             new_sp_row.standing_penalty := 1;
8446             RETURN NEXT new_sp_row;
8447         END IF;
8448     END IF;
8449
8450     -- Start over for max overdue
8451     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8452
8453     -- Fail if the user has too many overdue items
8454     LOOP
8455         tmp_grp := user_object.profile;
8456         LOOP
8457
8458             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8459
8460             IF max_overdue.threshold IS NULL THEN
8461                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8462             ELSE
8463                 EXIT;
8464             END IF;
8465
8466             IF tmp_grp IS NULL THEN
8467                 EXIT;
8468             END IF;
8469         END LOOP;
8470
8471         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8472             EXIT;
8473         END IF;
8474
8475         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8476
8477     END LOOP;
8478
8479     IF max_overdue.threshold IS NOT NULL THEN
8480
8481         FOR existing_sp_row IN
8482                 SELECT  *
8483                   FROM  actor.usr_standing_penalty
8484                   WHERE usr = match_user
8485                         AND org_unit = max_overdue.org_unit
8486                         AND (stop_date IS NULL or stop_date > NOW())
8487                         AND standing_penalty = 2
8488                 LOOP
8489             RETURN NEXT existing_sp_row;
8490         END LOOP;
8491
8492         SELECT  INTO items_overdue COUNT(*)
8493           FROM  action.circulation circ
8494                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8495           WHERE circ.usr = match_user
8496             AND circ.checkin_time IS NULL
8497             AND circ.due_date < NOW()
8498             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8499
8500         IF items_overdue >= max_overdue.threshold::INT THEN
8501             new_sp_row.usr := match_user;
8502             new_sp_row.org_unit := max_overdue.org_unit;
8503             new_sp_row.standing_penalty := 2;
8504             RETURN NEXT new_sp_row;
8505         END IF;
8506     END IF;
8507
8508     -- Start over for max out
8509     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8510
8511     -- Fail if the user has too many checked out items
8512     LOOP
8513         tmp_grp := user_object.profile;
8514         LOOP
8515             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8516
8517             IF max_items_out.threshold IS NULL THEN
8518                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8519             ELSE
8520                 EXIT;
8521             END IF;
8522
8523             IF tmp_grp IS NULL THEN
8524                 EXIT;
8525             END IF;
8526         END LOOP;
8527
8528         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8529             EXIT;
8530         END IF;
8531
8532         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8533
8534     END LOOP;
8535
8536
8537     -- Fail if the user has too many items checked out
8538     IF max_items_out.threshold IS NOT NULL THEN
8539
8540         FOR existing_sp_row IN
8541                 SELECT  *
8542                   FROM  actor.usr_standing_penalty
8543                   WHERE usr = match_user
8544                         AND org_unit = max_items_out.org_unit
8545                         AND (stop_date IS NULL or stop_date > NOW())
8546                         AND standing_penalty = 3
8547                 LOOP
8548             RETURN NEXT existing_sp_row;
8549         END LOOP;
8550
8551         SELECT  INTO items_out COUNT(*)
8552           FROM  action.circulation circ
8553                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8554           WHERE circ.usr = match_user
8555                 AND circ.checkin_time IS NULL
8556                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8557
8558            IF items_out >= max_items_out.threshold::INT THEN
8559             new_sp_row.usr := match_user;
8560             new_sp_row.org_unit := max_items_out.org_unit;
8561             new_sp_row.standing_penalty := 3;
8562             RETURN NEXT new_sp_row;
8563            END IF;
8564     END IF;
8565
8566     -- Start over for collections warning
8567     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8568
8569     -- Fail if the user has a collections-level fine balance
8570     LOOP
8571         tmp_grp := user_object.profile;
8572         LOOP
8573             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8574
8575             IF max_fines.threshold IS NULL THEN
8576                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8577             ELSE
8578                 EXIT;
8579             END IF;
8580
8581             IF tmp_grp IS NULL THEN
8582                 EXIT;
8583             END IF;
8584         END LOOP;
8585
8586         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8587             EXIT;
8588         END IF;
8589
8590         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8591
8592     END LOOP;
8593
8594     IF max_fines.threshold IS NOT NULL THEN
8595
8596         FOR existing_sp_row IN
8597                 SELECT  *
8598                   FROM  actor.usr_standing_penalty
8599                   WHERE usr = match_user
8600                         AND org_unit = max_fines.org_unit
8601                         AND (stop_date IS NULL or stop_date > NOW())
8602                         AND standing_penalty = 4
8603                 LOOP
8604             RETURN NEXT existing_sp_row;
8605         END LOOP;
8606
8607         SELECT  SUM(f.balance_owed) INTO current_fines
8608           FROM  money.materialized_billable_xact_summary f
8609                 JOIN (
8610                     SELECT  r.id
8611                       FROM  booking.reservation r
8612                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8613                       WHERE usr = match_user
8614                             AND xact_finish IS NULL
8615                                 UNION ALL
8616                     SELECT  g.id
8617                       FROM  money.grocery g
8618                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8619                       WHERE usr = match_user
8620                             AND xact_finish IS NULL
8621                                 UNION ALL
8622                     SELECT  circ.id
8623                       FROM  action.circulation circ
8624                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8625                       WHERE usr = match_user
8626                             AND xact_finish IS NULL ) l USING (id);
8627
8628         IF current_fines >= max_fines.threshold THEN
8629             new_sp_row.usr := match_user;
8630             new_sp_row.org_unit := max_fines.org_unit;
8631             new_sp_row.standing_penalty := 4;
8632             RETURN NEXT new_sp_row;
8633         END IF;
8634     END IF;
8635
8636     -- Start over for in collections
8637     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8638
8639     -- Remove the in-collections penalty if the user has paid down enough
8640     -- This penalty is different, because this code is not responsible for creating 
8641     -- new in-collections penalties, only for removing them
8642     LOOP
8643         tmp_grp := user_object.profile;
8644         LOOP
8645             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8646
8647             IF max_fines.threshold IS NULL THEN
8648                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8649             ELSE
8650                 EXIT;
8651             END IF;
8652
8653             IF tmp_grp IS NULL THEN
8654                 EXIT;
8655             END IF;
8656         END LOOP;
8657
8658         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8659             EXIT;
8660         END IF;
8661
8662         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8663
8664     END LOOP;
8665
8666     IF max_fines.threshold IS NOT NULL THEN
8667
8668         -- first, see if the user had paid down to the threshold
8669         SELECT  SUM(f.balance_owed) INTO current_fines
8670           FROM  money.materialized_billable_xact_summary f
8671                 JOIN (
8672                     SELECT  r.id
8673                       FROM  booking.reservation r
8674                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8675                       WHERE usr = match_user
8676                             AND xact_finish IS NULL
8677                                 UNION ALL
8678                     SELECT  g.id
8679                       FROM  money.grocery g
8680                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8681                       WHERE usr = match_user
8682                             AND xact_finish IS NULL
8683                                 UNION ALL
8684                     SELECT  circ.id
8685                       FROM  action.circulation circ
8686                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8687                       WHERE usr = match_user
8688                             AND xact_finish IS NULL ) l USING (id);
8689
8690         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8691             -- patron has paid down enough
8692
8693             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8694
8695             IF tmp_penalty.org_depth IS NOT NULL THEN
8696
8697                 -- since this code is not responsible for applying the penalty, it can't 
8698                 -- guarantee the current context org will match the org at which the penalty 
8699                 --- was applied.  search up the org tree until we hit the configured penalty depth
8700                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8701                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8702
8703                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8704
8705                     FOR existing_sp_row IN
8706                             SELECT  *
8707                             FROM  actor.usr_standing_penalty
8708                             WHERE usr = match_user
8709                                     AND org_unit = tmp_org.id
8710                                     AND (stop_date IS NULL or stop_date > NOW())
8711                                     AND standing_penalty = 30 
8712                             LOOP
8713
8714                         -- Penalty exists, return it for removal
8715                         RETURN NEXT existing_sp_row;
8716                     END LOOP;
8717
8718                     IF tmp_org.parent_ou IS NULL THEN
8719                         EXIT;
8720                     END IF;
8721
8722                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8723                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8724                 END LOOP;
8725
8726             ELSE
8727
8728                 -- no penalty depth is defined, look for exact matches
8729
8730                 FOR existing_sp_row IN
8731                         SELECT  *
8732                         FROM  actor.usr_standing_penalty
8733                         WHERE usr = match_user
8734                                 AND org_unit = max_fines.org_unit
8735                                 AND (stop_date IS NULL or stop_date > NOW())
8736                                 AND standing_penalty = 30 
8737                         LOOP
8738                     -- Penalty exists, return it for removal
8739                     RETURN NEXT existing_sp_row;
8740                 END LOOP;
8741             END IF;
8742     
8743         END IF;
8744
8745     END IF;
8746
8747     RETURN;
8748 END;
8749 $func$ LANGUAGE plpgsql;
8750
8751 -- Create a default row in acq.fiscal_calendar
8752 -- Add a column in actor.org_unit to point to it
8753
8754 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8755
8756 ALTER TABLE actor.org_unit
8757 ADD COLUMN fiscal_calendar INT NOT NULL
8758         REFERENCES acq.fiscal_calendar( id )
8759         DEFERRABLE INITIALLY DEFERRED
8760         DEFAULT 1;
8761
8762 ALTER TABLE auditor.actor_org_unit_history
8763         ADD COLUMN fiscal_calendar INT;
8764
8765 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8766
8767 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8768
8769 ALTER TABLE acq.funding_source_credit
8770 ADD COLUMN deadline_date TIMESTAMPTZ;
8771
8772 ALTER TABLE acq.funding_source_credit
8773 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8774
8775 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8776
8777 CREATE TABLE acq.fund_transfer (
8778         id               SERIAL         PRIMARY KEY,
8779         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8780                                         DEFERRABLE INITIALLY DEFERRED,
8781         src_amount       NUMERIC        NOT NULL,
8782         dest_fund        INT            REFERENCES acq.fund( id )
8783                                         DEFERRABLE INITIALLY DEFERRED,
8784         dest_amount      NUMERIC,
8785         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
8786         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
8787                                         DEFERRABLE INITIALLY DEFERRED,
8788         note             TEXT,
8789     funding_source_credit INTEGER   NOT NULL
8790                                         REFERENCES acq.funding_source_credit(id)
8791                                         DEFERRABLE INITIALLY DEFERRED
8792 );
8793
8794 CREATE INDEX acqftr_usr_idx
8795 ON acq.fund_transfer( transfer_user );
8796
8797 COMMENT ON TABLE acq.fund_transfer IS $$
8798 /*
8799  * Copyright (C) 2009  Georgia Public Library Service
8800  * Scott McKellar <scott@esilibrary.com>
8801  *
8802  * Fund Transfer
8803  *
8804  * Each row represents the transfer of money from a source fund
8805  * to a destination fund.  There should be corresponding entries
8806  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
8807  * to record how much money moved from which fund to which other
8808  * fund.
8809  * 
8810  * The presence of two amount fields, rather than one, reflects
8811  * the possibility that the two funds are denominated in different
8812  * currencies.  If they use the same currency type, the two
8813  * amounts should be the same.
8814  *
8815  * ****
8816  *
8817  * This program is free software; you can redistribute it and/or
8818  * modify it under the terms of the GNU General Public License
8819  * as published by the Free Software Foundation; either version 2
8820  * of the License, or (at your option) any later version.
8821  *
8822  * This program is distributed in the hope that it will be useful,
8823  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8824  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
8825  * GNU General Public License for more details.
8826  */
8827 $$;
8828
8829 CREATE TABLE acq.claim_event_type (
8830         id             SERIAL           PRIMARY KEY,
8831         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8832                                                  DEFERRABLE INITIALLY DEFERRED,
8833         code           TEXT             NOT NULL,
8834         description    TEXT             NOT NULL,
8835         library_initiated BOOL          NOT NULL DEFAULT FALSE,
8836         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
8837 );
8838
8839 CREATE TABLE acq.claim_event (
8840         id             BIGSERIAL        PRIMARY KEY,
8841         type           INT              NOT NULL REFERENCES acq.claim_event_type
8842                                                  DEFERRABLE INITIALLY DEFERRED,
8843         claim          SERIAL           NOT NULL REFERENCES acq.claim
8844                                                  DEFERRABLE INITIALLY DEFERRED,
8845         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
8846         creator        INT              NOT NULL REFERENCES actor.usr
8847                                                  DEFERRABLE INITIALLY DEFERRED,
8848         note           TEXT
8849 );
8850
8851 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
8852
8853 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
8854         src_usr  IN INTEGER,
8855         dest_usr IN INTEGER
8856 ) RETURNS VOID AS $$
8857 DECLARE
8858         suffix TEXT;
8859         renamable_row RECORD;
8860 BEGIN
8861
8862         UPDATE actor.usr SET
8863                 active = FALSE,
8864                 card = NULL,
8865                 mailing_address = NULL,
8866                 billing_address = NULL
8867         WHERE id = src_usr;
8868
8869         -- acq.*
8870         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
8871         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
8872         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
8873         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
8874         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
8875         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
8876         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
8877
8878         -- Update with a rename to avoid collisions
8879         FOR renamable_row in
8880                 SELECT id, name
8881                 FROM   acq.picklist
8882                 WHERE  owner = src_usr
8883         LOOP
8884                 suffix := ' (' || src_usr || ')';
8885                 LOOP
8886                         BEGIN
8887                                 UPDATE  acq.picklist
8888                                 SET     owner = dest_usr, name = name || suffix
8889                                 WHERE   id = renamable_row.id;
8890                         EXCEPTION WHEN unique_violation THEN
8891                                 suffix := suffix || ' ';
8892                                 CONTINUE;
8893                         END;
8894                         EXIT;
8895                 END LOOP;
8896         END LOOP;
8897
8898         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
8899         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
8900         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
8901         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
8902         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
8903         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
8904         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
8905         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
8906
8907         -- action.*
8908         DELETE FROM action.circulation WHERE usr = src_usr;
8909         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
8910         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
8911         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
8912         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
8913         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
8914         DELETE FROM action.hold_request WHERE usr = src_usr;
8915         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
8916         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
8917         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
8918         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
8919         DELETE FROM action.survey_response WHERE usr = src_usr;
8920         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
8921
8922         -- actor.*
8923         DELETE FROM actor.card WHERE usr = src_usr;
8924         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
8925
8926         -- The following update is intended to avoid transient violations of a foreign
8927         -- key constraint, whereby actor.usr_address references itself.  It may not be
8928         -- necessary, but it does no harm.
8929         UPDATE actor.usr_address SET replaces = NULL
8930                 WHERE usr = src_usr AND replaces IS NOT NULL;
8931         DELETE FROM actor.usr_address WHERE usr = src_usr;
8932         DELETE FROM actor.usr_note WHERE usr = src_usr;
8933         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
8934         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
8935         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
8936         DELETE FROM actor.usr_setting WHERE usr = src_usr;
8937         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
8938         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
8939
8940         -- asset.*
8941         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
8942         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
8943         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
8944         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
8945         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
8946         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
8947
8948         -- auditor.*
8949         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
8950         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
8951         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
8952         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
8953         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
8954         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
8955         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
8956         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
8957
8958         -- biblio.*
8959         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
8960         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
8961         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
8962         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
8963
8964         -- container.*
8965         -- Update buckets with a rename to avoid collisions
8966         FOR renamable_row in
8967                 SELECT id, name
8968                 FROM   container.biblio_record_entry_bucket
8969                 WHERE  owner = src_usr
8970         LOOP
8971                 suffix := ' (' || src_usr || ')';
8972                 LOOP
8973                         BEGIN
8974                                 UPDATE  container.biblio_record_entry_bucket
8975                                 SET     owner = dest_usr, name = name || suffix
8976                                 WHERE   id = renamable_row.id;
8977                         EXCEPTION WHEN unique_violation THEN
8978                                 suffix := suffix || ' ';
8979                                 CONTINUE;
8980                         END;
8981                         EXIT;
8982                 END LOOP;
8983         END LOOP;
8984
8985         FOR renamable_row in
8986                 SELECT id, name
8987                 FROM   container.call_number_bucket
8988                 WHERE  owner = src_usr
8989         LOOP
8990                 suffix := ' (' || src_usr || ')';
8991                 LOOP
8992                         BEGIN
8993                                 UPDATE  container.call_number_bucket
8994                                 SET     owner = dest_usr, name = name || suffix
8995                                 WHERE   id = renamable_row.id;
8996                         EXCEPTION WHEN unique_violation THEN
8997                                 suffix := suffix || ' ';
8998                                 CONTINUE;
8999                         END;
9000                         EXIT;
9001                 END LOOP;
9002         END LOOP;
9003
9004         FOR renamable_row in
9005                 SELECT id, name
9006                 FROM   container.copy_bucket
9007                 WHERE  owner = src_usr
9008         LOOP
9009                 suffix := ' (' || src_usr || ')';
9010                 LOOP
9011                         BEGIN
9012                                 UPDATE  container.copy_bucket
9013                                 SET     owner = dest_usr, name = name || suffix
9014                                 WHERE   id = renamable_row.id;
9015                         EXCEPTION WHEN unique_violation THEN
9016                                 suffix := suffix || ' ';
9017                                 CONTINUE;
9018                         END;
9019                         EXIT;
9020                 END LOOP;
9021         END LOOP;
9022
9023         FOR renamable_row in
9024                 SELECT id, name
9025                 FROM   container.user_bucket
9026                 WHERE  owner = src_usr
9027         LOOP
9028                 suffix := ' (' || src_usr || ')';
9029                 LOOP
9030                         BEGIN
9031                                 UPDATE  container.user_bucket
9032                                 SET     owner = dest_usr, name = name || suffix
9033                                 WHERE   id = renamable_row.id;
9034                         EXCEPTION WHEN unique_violation THEN
9035                                 suffix := suffix || ' ';
9036                                 CONTINUE;
9037                         END;
9038                         EXIT;
9039                 END LOOP;
9040         END LOOP;
9041
9042         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
9043
9044         -- money.*
9045         DELETE FROM money.billable_xact WHERE usr = src_usr;
9046         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9047         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9048
9049         -- permission.*
9050         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9051         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9052         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9053         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9054
9055         -- reporter.*
9056         -- Update with a rename to avoid collisions
9057         BEGIN
9058                 FOR renamable_row in
9059                         SELECT id, name
9060                         FROM   reporter.output_folder
9061                         WHERE  owner = src_usr
9062                 LOOP
9063                         suffix := ' (' || src_usr || ')';
9064                         LOOP
9065                                 BEGIN
9066                                         UPDATE  reporter.output_folder
9067                                         SET     owner = dest_usr, name = name || suffix
9068                                         WHERE   id = renamable_row.id;
9069                                 EXCEPTION WHEN unique_violation THEN
9070                                         suffix := suffix || ' ';
9071                                         CONTINUE;
9072                                 END;
9073                                 EXIT;
9074                         END LOOP;
9075                 END LOOP;
9076         EXCEPTION WHEN undefined_table THEN
9077                 -- do nothing
9078         END;
9079
9080         BEGIN
9081                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9082         EXCEPTION WHEN undefined_table THEN
9083                 -- do nothing
9084         END;
9085
9086         -- Update with a rename to avoid collisions
9087         BEGIN
9088                 FOR renamable_row in
9089                         SELECT id, name
9090                         FROM   reporter.report_folder
9091                         WHERE  owner = src_usr
9092                 LOOP
9093                         suffix := ' (' || src_usr || ')';
9094                         LOOP
9095                                 BEGIN
9096                                         UPDATE  reporter.report_folder
9097                                         SET     owner = dest_usr, name = name || suffix
9098                                         WHERE   id = renamable_row.id;
9099                                 EXCEPTION WHEN unique_violation THEN
9100                                         suffix := suffix || ' ';
9101                                         CONTINUE;
9102                                 END;
9103                                 EXIT;
9104                         END LOOP;
9105                 END LOOP;
9106         EXCEPTION WHEN undefined_table THEN
9107                 -- do nothing
9108         END;
9109
9110         BEGIN
9111                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9112         EXCEPTION WHEN undefined_table THEN
9113                 -- do nothing
9114         END;
9115
9116         BEGIN
9117                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9118         EXCEPTION WHEN undefined_table THEN
9119                 -- do nothing
9120         END;
9121
9122         -- Update with a rename to avoid collisions
9123         BEGIN
9124                 FOR renamable_row in
9125                         SELECT id, name
9126                         FROM   reporter.template_folder
9127                         WHERE  owner = src_usr
9128                 LOOP
9129                         suffix := ' (' || src_usr || ')';
9130                         LOOP
9131                                 BEGIN
9132                                         UPDATE  reporter.template_folder
9133                                         SET     owner = dest_usr, name = name || suffix
9134                                         WHERE   id = renamable_row.id;
9135                                 EXCEPTION WHEN unique_violation THEN
9136                                         suffix := suffix || ' ';
9137                                         CONTINUE;
9138                                 END;
9139                                 EXIT;
9140                         END LOOP;
9141                 END LOOP;
9142         EXCEPTION WHEN undefined_table THEN
9143         -- do nothing
9144         END;
9145
9146         -- vandelay.*
9147         -- Update with a rename to avoid collisions
9148         FOR renamable_row in
9149                 SELECT id, name
9150                 FROM   vandelay.queue
9151                 WHERE  owner = src_usr
9152         LOOP
9153                 suffix := ' (' || src_usr || ')';
9154                 LOOP
9155                         BEGIN
9156                                 UPDATE  vandelay.queue
9157                                 SET     owner = dest_usr, name = name || suffix
9158                                 WHERE   id = renamable_row.id;
9159                         EXCEPTION WHEN unique_violation THEN
9160                                 suffix := suffix || ' ';
9161                                 CONTINUE;
9162                         END;
9163                         EXIT;
9164                 END LOOP;
9165         END LOOP;
9166
9167 END;
9168 $$ LANGUAGE plpgsql;
9169
9170 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9171 /**
9172  * Finds rows dependent on a given row in actor.usr and either deletes them
9173  * or reassigns them to a different user.
9174  */
9175 $$;
9176
9177 CREATE OR REPLACE FUNCTION actor.usr_delete(
9178         src_usr  IN INTEGER,
9179         dest_usr IN INTEGER
9180 ) RETURNS VOID AS $$
9181 DECLARE
9182         old_profile actor.usr.profile%type;
9183         old_home_ou actor.usr.home_ou%type;
9184         new_profile actor.usr.profile%type;
9185         new_home_ou actor.usr.home_ou%type;
9186         new_name    text;
9187         new_dob     actor.usr.dob%type;
9188 BEGIN
9189         SELECT
9190                 id || '-PURGED-' || now(),
9191                 profile,
9192                 home_ou,
9193                 dob
9194         INTO
9195                 new_name,
9196                 old_profile,
9197                 old_home_ou,
9198                 new_dob
9199         FROM
9200                 actor.usr
9201         WHERE
9202                 id = src_usr;
9203         --
9204         -- Quit if no such user
9205         --
9206         IF old_profile IS NULL THEN
9207                 RETURN;
9208         END IF;
9209         --
9210         perform actor.usr_purge_data( src_usr, dest_usr );
9211         --
9212         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9213         -- could assume that there is only one root.  Theoretically, someday, maybe,
9214         -- there could be multiple roots, so we take extra trouble to get the right ones.
9215         --
9216         SELECT
9217                 id
9218         INTO
9219                 new_profile
9220         FROM
9221                 permission.grp_ancestors( old_profile )
9222         WHERE
9223                 parent is null;
9224         --
9225         SELECT
9226                 id
9227         INTO
9228                 new_home_ou
9229         FROM
9230                 actor.org_unit_ancestors( old_home_ou )
9231         WHERE
9232                 parent_ou is null;
9233         --
9234         -- Truncate date of birth
9235         --
9236         IF new_dob IS NOT NULL THEN
9237                 new_dob := date_trunc( 'year', new_dob );
9238         END IF;
9239         --
9240         UPDATE
9241                 actor.usr
9242                 SET
9243                         card = NULL,
9244                         profile = new_profile,
9245                         usrname = new_name,
9246                         email = NULL,
9247                         passwd = random()::text,
9248                         standing = DEFAULT,
9249                         ident_type = 
9250                         (
9251                                 SELECT MIN( id )
9252                                 FROM config.identification_type
9253                         ),
9254                         ident_value = NULL,
9255                         ident_type2 = NULL,
9256                         ident_value2 = NULL,
9257                         net_access_level = DEFAULT,
9258                         photo_url = NULL,
9259                         prefix = NULL,
9260                         first_given_name = new_name,
9261                         second_given_name = NULL,
9262                         family_name = new_name,
9263                         suffix = NULL,
9264                         alias = NULL,
9265                         day_phone = NULL,
9266                         evening_phone = NULL,
9267                         other_phone = NULL,
9268                         mailing_address = NULL,
9269                         billing_address = NULL,
9270                         home_ou = new_home_ou,
9271                         dob = new_dob,
9272                         active = FALSE,
9273                         master_account = DEFAULT, 
9274                         super_user = DEFAULT,
9275                         barred = FALSE,
9276                         deleted = TRUE,
9277                         juvenile = DEFAULT,
9278                         usrgroup = 0,
9279                         claims_returned_count = DEFAULT,
9280                         credit_forward_balance = DEFAULT,
9281                         last_xact_id = DEFAULT,
9282                         alert_message = NULL,
9283                         create_date = now(),
9284                         expire_date = now()
9285         WHERE
9286                 id = src_usr;
9287 END;
9288 $$ LANGUAGE plpgsql;
9289
9290 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9291 /**
9292  * Logically deletes a user.  Removes personally identifiable information,
9293  * and purges associated data in other tables.
9294  */
9295 $$;
9296
9297 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9298
9299 ALTER TABLE acq.fund
9300 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9301
9302 ALTER TABLE acq.fund
9303         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9304
9305 -- A fund can't roll over if it doesn't propagate from one year to the next
9306
9307 ALTER TABLE acq.fund
9308         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9309         ( propagate OR NOT rollover );
9310
9311 ALTER TABLE acq.fund
9312         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9313
9314 ALTER TABLE acq.fund
9315     ADD COLUMN balance_warning_percent INT
9316     CONSTRAINT balance_warning_percent_limit
9317         CHECK( balance_warning_percent <= 100 );
9318
9319 ALTER TABLE acq.fund
9320     ADD COLUMN balance_stop_percent INT
9321     CONSTRAINT balance_stop_percent_limit
9322         CHECK( balance_stop_percent <= 100 );
9323
9324 CREATE VIEW acq.ordered_funding_source_credit AS
9325         SELECT
9326                 CASE WHEN deadline_date IS NULL THEN
9327                         2
9328                 ELSE
9329                         1
9330                 END AS sort_priority,
9331                 CASE WHEN deadline_date IS NULL THEN
9332                         effective_date
9333                 ELSE
9334                         deadline_date
9335                 END AS sort_date,
9336                 id,
9337                 funding_source,
9338                 amount,
9339                 note
9340         FROM
9341                 acq.funding_source_credit;
9342
9343 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9344 /*
9345  * Copyright (C) 2009  Georgia Public Library Service
9346  * Scott McKellar <scott@gmail.com>
9347  *
9348  * The acq.ordered_funding_source_credit view is a prioritized
9349  * ordering of funding source credits.  When ordered by the first
9350  * three columns, this view defines the order in which the various
9351  * credits are to be tapped for spending, subject to the allocations
9352  * in the acq.fund_allocation table.
9353  *
9354  * The first column reflects the principle that we should spend
9355  * money with deadlines before spending money without deadlines.
9356  *
9357  * The second column reflects the principle that we should spend the
9358  * oldest money first.  For money with deadlines, that means that we
9359  * spend first from the credit with the earliest deadline.  For
9360  * money without deadlines, we spend first from the credit with the
9361  * earliest effective date.  
9362  *
9363  * The third column is a tie breaker to ensure a consistent
9364  * ordering.
9365  *
9366  * ****
9367  *
9368  * This program is free software; you can redistribute it and/or
9369  * modify it under the terms of the GNU General Public License
9370  * as published by the Free Software Foundation; either version 2
9371  * of the License, or (at your option) any later version.
9372  *
9373  * This program is distributed in the hope that it will be useful,
9374  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9375  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9376  * GNU General Public License for more details.
9377  */
9378 $$;
9379
9380 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9381     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9382       FROM  money.materialized_billable_xact_summary m
9383             LEFT JOIN action.circulation c ON (c.id = m.id)
9384             LEFT JOIN money.grocery g ON (g.id = m.id)
9385             LEFT JOIN booking.reservation r ON (r.id = m.id);
9386
9387 CREATE TABLE config.marc21_rec_type_map (
9388     code        TEXT    PRIMARY KEY,
9389     type_val    TEXT    NOT NULL,
9390     blvl_val    TEXT    NOT NULL
9391 );
9392
9393 CREATE TABLE config.marc21_ff_pos_map (
9394     id          SERIAL  PRIMARY KEY,
9395     fixed_field TEXT    NOT NULL,
9396     tag         TEXT    NOT NULL,
9397     rec_type    TEXT    NOT NULL,
9398     start_pos   INT     NOT NULL,
9399     length      INT     NOT NULL,
9400     default_val TEXT    NOT NULL DEFAULT ' '
9401 );
9402
9403 CREATE TABLE config.marc21_physical_characteristic_type_map (
9404     ptype_key   TEXT    PRIMARY KEY,
9405     label       TEXT    NOT NULL -- I18N
9406 );
9407
9408 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9409     id          SERIAL  PRIMARY KEY,
9410     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9411     subfield    TEXT    NOT NULL,
9412     start_pos   INT     NOT NULL,
9413     length      INT     NOT NULL,
9414     label       TEXT    NOT NULL -- I18N
9415 );
9416
9417 CREATE TABLE config.marc21_physical_characteristic_value_map (
9418     id              SERIAL  PRIMARY KEY,
9419     value           TEXT    NOT NULL,
9420     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9421     label           TEXT    NOT NULL -- I18N
9422 );
9423
9424 ----------------------------------
9425 -- MARC21 record structure data --
9426 ----------------------------------
9427
9428 -- Record type map
9429 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9430 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9431 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9432 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9433 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9434 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9435 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9436 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9437
9438 ------ Physical Characteristics
9439
9440 -- Map
9441 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9442 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9443 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9444 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9445 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9446 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9447 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9448 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');
9449 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9450 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9451 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9452 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9453 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9454 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');
9455 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9456 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9457 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9458 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9459 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9460 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9461 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9462 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9463 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9464 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9465 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');
9466 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');
9467 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');
9468 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');
9469 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9470 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');
9471 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9472 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9473 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9474 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');
9475 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
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 ('a','g','6','1','Production/reproduction details');
9478 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');
9479 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9480 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');
9481 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
9482 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9483 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9484 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9485 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9486 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9487 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9488 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');
9489
9490 -- Electronic Resource
9491 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9492 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9493 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');
9494 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');
9495 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');
9496 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');
9497 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');
9498 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');
9499 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');
9500 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');
9501 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9502 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9503 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9504 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
9505 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');
9506 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');
9507 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9508 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');
9509 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
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 ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9512 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9513 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9514 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.');
9515 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.');
9516 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.');
9517 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.');
9518 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.');
9519 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');
9520 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.');
9521 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9522 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.');
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 ('c','f','5','1','Sound');
9525 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)');
9526 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
9527 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9528 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9529 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9530 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9531 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');
9532 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9533 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');
9534 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');
9535 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9536 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9537 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9538 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');
9539 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
9540 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9541 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
9542 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');
9543 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');
9544 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');
9545 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)');
9546 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9547 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');
9548 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9549 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9550 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9551 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9552 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9553 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9554 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9555 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
9556 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9557 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');
9558 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9559 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9560 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9561
9562 -- Globe
9563 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9564 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9565 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');
9566 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');
9567 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');
9568 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');
9569 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9570 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9571 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
9572 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');
9573 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9574 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9575 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9576 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9577 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9578 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9579 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9580 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9581 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9582 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9583 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9584 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9585 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9586 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9587 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');
9588 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9589 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9590
9591 -- Tactile Material
9592 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9593 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9594 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9595 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9596 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9597 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');
9598 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9599 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9600 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9601 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');
9602 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');
9603 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');
9604 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');
9605 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');
9606 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');
9607 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');
9608 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9609 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9610 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9611 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9612 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9613 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9614 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');
9615 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9616 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9617 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9618 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');
9619 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');
9620 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');
9621 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9622 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');
9623 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');
9624 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');
9625 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');
9626 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');
9627 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');
9628 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9629 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');
9630 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');
9631 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9632 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9633 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
9634 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');
9635 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');
9636 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');
9637 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9638 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9639
9640 -- Projected Graphic
9641 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9642 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9643 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');
9644 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9645 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');
9646 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');
9647 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9648 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
9649 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9650 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9651 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');
9652 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9653 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');
9654 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9655 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');
9656 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9657 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9658 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9659 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9660 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9661 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');
9662 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');
9663 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');
9664 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9665 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9666 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9667 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');
9668 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');
9669 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');
9670 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9671 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9672 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');
9673 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');
9674 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');
9675 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');
9676 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');
9677 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');
9678 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');
9679 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9680 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
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_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9683 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9684 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.');
9685 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.');
9686 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.');
9687 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.');
9688 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.');
9689 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.');
9690 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.');
9691 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.)');
9692 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.)');
9693 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.)');
9694 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.)');
9695 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9696 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.)');
9697 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.)');
9698 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.)');
9699 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.)');
9700 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9701 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9702 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9703 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9704 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9705 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9706 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');
9707 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');
9708 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');
9709 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9710 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9711
9712 -- Microform
9713 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9714 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9715 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');
9716 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');
9717 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');
9718 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');
9719 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9720 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');
9721 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9722 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9723 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9724 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9725 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9726 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
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');
9728 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9729 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
9730 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.');
9731 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.');
9732 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.');
9733 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9734 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.');
9735 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.)');
9736 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.)');
9737 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.)');
9738 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.)');
9739 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9740 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9741 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');
9742 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)');
9743 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)');
9744 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)');
9745 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)');
9746 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-)');
9747 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9748 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');
9749 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9750 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');
9751 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9752 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9753 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9754 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9755 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9756 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');
9757 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9758 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9759 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9760 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');
9761 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9762 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9763 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
9764 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');
9765 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');
9766 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');
9767 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');
9768 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9769 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9770 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');
9771 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');
9772 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');
9773 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');
9774 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');
9775 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');
9776 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');
9777 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');
9778 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');
9779 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9780 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9781
9782 -- Non-projected Graphic
9783 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9784 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
9785 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
9786 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
9787 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
9788 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');
9789 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
9790 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
9791 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
9792 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
9793 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');
9794 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
9795 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');
9796 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9797 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9798 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
9799 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');
9800 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');
9801 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9802 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');
9803 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9804 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9805 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9806 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
9807 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9808 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');
9809 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');
9810 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9811 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9812 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9813 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9814 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9815 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');
9816 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9817 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9818 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9819 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9820 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9821 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9822 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9823 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9824 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
9825 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9826 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');
9827 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');
9828 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9829 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9830 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9831 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9832 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9833 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9834 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9835 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9836 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9837 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9838 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9839 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9840 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9841 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9842
9843 -- Motion Picture
9844 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
9845 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
9846 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');
9847 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');
9848 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');
9849 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9850 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9851 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
9852 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');
9853 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9854 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');
9855 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9856 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9857 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9858 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
9859 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');
9860 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)');
9861 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
9862 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)');
9863 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');
9864 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');
9865 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9866 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9867 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');
9868 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');
9869 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');
9870 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9871 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
9872 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');
9873 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');
9874 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');
9875 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');
9876 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');
9877 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');
9878 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');
9879 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9880 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9881 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9882 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9883 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
9884 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.');
9885 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.');
9886 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.');
9887 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.');
9888 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.');
9889 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.');
9890 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.');
9891 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9892 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9893 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
9894 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9895 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
9896 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');
9897 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');
9898 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
9899 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9900 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9901 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
9902 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');
9903 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
9904 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
9905 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
9906 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');
9907 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');
9908 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');
9909 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9910 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9911
9912 -- Remote-sensing Image
9913 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
9914 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
9915 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9916 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
9917 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
9918 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
9919 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
9920 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');
9921 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9922 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9923 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
9924 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');
9925 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');
9926 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
9927 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');
9928 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9929 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
9930 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%');
9931 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%');
9932 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%');
9933 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%');
9934 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%');
9935 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%');
9936 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%');
9937 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%');
9938 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%');
9939 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%');
9940 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');
9941 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9942 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
9943 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
9944 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');
9945 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');
9946 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');
9947 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');
9948 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');
9949 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');
9950 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');
9951 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');
9952 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');
9953 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9954 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9955 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
9956 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
9957 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');
9958 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');
9959 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');
9960 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');
9961 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9962 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9963 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
9964 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
9965 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
9966 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9967 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9968 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
9969 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');
9970 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');
9971 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');
9972 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');
9973 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');
9974 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)');
9975 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');
9976 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
9977 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');
9978 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)');
9979 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)');
9980 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)');
9981 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');
9982 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');
9983 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');
9984 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');
9985 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');
9986 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');
9987 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');
9988 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');
9989 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');
9990 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');
9991 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');
9992 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');
9993 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');
9994 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');
9995 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');
9996 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');
9997 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');
9998 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');
9999 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');
10000 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');
10001 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');
10002 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)');
10003 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');
10004 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
10005 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
10006 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');
10007 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');
10008 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10009 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10010
10011 -- Sound Recording
10012 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
10013 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
10014 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');
10015 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
10016 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');
10017 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');
10018 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
10019 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');
10020 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');
10021 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10022 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');
10023 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10024 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
10025 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');
10026 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');
10027 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');
10028 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');
10029 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');
10030 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');
10031 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');
10032 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');
10033 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');
10034 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');
10035 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');
10036 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');
10037 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');
10038 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');
10039 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10040 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10041 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
10042 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10043 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
10044 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10045 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10046 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10047 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10048 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');
10049 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');
10050 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');
10051 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10052 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10053 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10054 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.');
10055 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.');
10056 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.');
10057 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.');
10058 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.');
10059 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.');
10060 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.)');
10061 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.');
10062 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');
10063 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.');
10064 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.');
10065 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
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 ('s','h','7','1','Tape width');
10068 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.');
10069 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.');
10070 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');
10071 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.');
10072 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.');
10073 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10074 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10075 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
10076 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');
10077 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');
10078 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');
10079 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');
10080 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');
10081 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');
10082 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');
10083 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10084 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10085 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10086 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');
10087 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');
10088 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');
10089 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');
10090 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');
10091 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');
10092 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');
10093 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');
10094 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10095 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10096 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10097 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
10098 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');
10099 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');
10100 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');
10101 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');
10102 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10103 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10104
10105 -- Videorecording
10106 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10107 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10108 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10109 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10110 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10111 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10112 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10113 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10114 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10115 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');
10116 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10117 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10118 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');
10119 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10120 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10121 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10122 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10123 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10124 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');
10125 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10126 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');
10127 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10128 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10129 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10130 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10131 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');
10132 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');
10133 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');
10134 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');
10135 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.');
10136 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.');
10137 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10138 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10139 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10140 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');
10141 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');
10142 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');
10143 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10144 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10145 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');
10146 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');
10147 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');
10148 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound disc');
10149 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic audio tape on reel');
10150 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');
10151 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');
10152 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10153 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10154 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10155 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10156 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10157 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.');
10158 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.');
10159 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.');
10160 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.');
10161 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.');
10162 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.');
10163 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10164 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10165 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10166 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10167 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10168 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');
10169 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');
10170 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10171 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10172 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10173
10174 -- Fixed Field position data -- 0-based!
10175 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10176 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10177 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10178 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10179 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10180 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10181 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10182 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10183 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10184 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10185 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10186 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10187 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10188 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10189 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10190 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10191 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10192 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10193 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10194 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10195 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10196 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10197 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10198 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10199 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10200 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10201 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10202 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10203 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10204 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10205 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10206 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10207 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10208 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10209 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10210 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10211 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10212 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10213 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10214 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10215 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10216 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10217 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10218 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10219 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10220 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10221 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10222 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10223 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10224 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10225 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10226 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10227 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10228 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10229 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10230 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10231 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10232 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10233 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10234 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10235 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10236 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10237 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10238 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10239 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10240 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10241 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10242 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10243 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10244 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10245 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10246 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10247 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10248 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10249 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10250 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10251 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10252 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10253 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10254 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10255 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10256 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10257 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10258 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10259 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10260 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10261 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10262 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10263 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10264 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10265 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10266 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10267 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10268 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10269 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10270 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10271 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10272 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10273 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10274 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10275 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10276 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10277 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10278 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10279 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10280 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10281 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10282 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10283 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10284 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10285 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10286 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10287 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10288 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10289 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10290 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10291 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10292 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10293 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10294 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10295 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10296 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10297 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10298 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10299 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10300 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10301 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10302 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10303 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10304 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10305 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10306 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10307 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10308 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10309 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');
10310 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');
10311 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10312 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10313 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10314 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10315 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10316 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10317 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10318 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10319 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10320 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10321
10322 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10323 DECLARE
10324         ldr         RECORD;
10325         tval        TEXT;
10326         tval_rec    RECORD;
10327         bval        TEXT;
10328         bval_rec    RECORD;
10329     retval      config.marc21_rec_type_map%ROWTYPE;
10330 BEGIN
10331     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10332
10333     IF ldr.id IS NULL THEN
10334         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10335         RETURN retval;
10336     END IF;
10337
10338     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10339     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10340
10341
10342     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10343     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10344
10345     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10346
10347     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10348
10349
10350     IF retval.code IS NULL THEN
10351         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10352     END IF;
10353
10354     RETURN retval;
10355 END;
10356 $func$ LANGUAGE PLPGSQL;
10357
10358 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10359 DECLARE
10360     rtype       TEXT;
10361     ff_pos      RECORD;
10362     tag_data    RECORD;
10363     val         TEXT;
10364 BEGIN
10365     rtype := (biblio.marc21_record_type( rid )).code;
10366     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10367         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10368             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10369             RETURN val;
10370         END LOOP;
10371         val := REPEAT( ff_pos.default_val, ff_pos.length );
10372         RETURN val;
10373     END LOOP;
10374
10375     RETURN NULL;
10376 END;
10377 $func$ LANGUAGE PLPGSQL;
10378
10379 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10380 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10381 DECLARE
10382     rowid   INT := 0;
10383     _007    RECORD;
10384     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10385     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10386     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10387     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10388 BEGIN
10389
10390     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10391
10392     IF _007.id IS NOT NULL THEN
10393         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10394
10395         IF ptype.ptype_key IS NOT NULL THEN
10396             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10397                 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 );
10398
10399                 IF pval.id IS NOT NULL THEN
10400                     rowid := rowid + 1;
10401                     retval.id := rowid;
10402                     retval.record := rid;
10403                     retval.ptype := ptype.ptype_key;
10404                     retval.subfield := psf.id;
10405                     retval.value := pval.id;
10406                     RETURN NEXT retval;
10407                 END IF;
10408
10409             END LOOP;
10410         END IF;
10411     END IF;
10412
10413     RETURN;
10414 END;
10415 $func$ LANGUAGE PLPGSQL;
10416
10417 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10418 DROP VIEW IF EXISTS money.open_usr_summary;
10419 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10420
10421 -- The view should supply defaults for numeric (amount) columns
10422 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10423     SELECT  xact.id,
10424         xact.usr,
10425         xact.xact_start,
10426         xact.xact_finish,
10427         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10428         credit.payment_ts AS last_payment_ts,
10429         credit.note AS last_payment_note,
10430         credit.payment_type AS last_payment_type,
10431         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10432         debit.billing_ts AS last_billing_ts,
10433         debit.note AS last_billing_note,
10434         debit.billing_type AS last_billing_type,
10435         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10436         p.relname AS xact_type
10437       FROM  money.billable_xact xact
10438         JOIN pg_class p ON xact.tableoid = p.oid
10439         LEFT JOIN (
10440             SELECT  billing.xact,
10441                 sum(billing.amount) AS amount,
10442                 max(billing.billing_ts) AS billing_ts,
10443                 last(billing.note) AS note,
10444                 last(billing.billing_type) AS billing_type
10445               FROM  money.billing
10446               WHERE billing.voided IS FALSE
10447               GROUP BY billing.xact
10448             ) debit ON xact.id = debit.xact
10449         LEFT JOIN (
10450             SELECT  payment_view.xact,
10451                 sum(payment_view.amount) AS amount,
10452                 max(payment_view.payment_ts) AS payment_ts,
10453                 last(payment_view.note) AS note,
10454                 last(payment_view.payment_type) AS payment_type
10455               FROM  money.payment_view
10456               WHERE payment_view.voided IS FALSE
10457               GROUP BY payment_view.xact
10458             ) credit ON xact.id = credit.xact
10459       ORDER BY debit.billing_ts, credit.payment_ts;
10460
10461 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10462     SELECT * FROM money.billable_xact_summary_location_view
10463     WHERE xact_finish IS NULL;
10464
10465 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10466     SELECT 
10467         usr,
10468         SUM(total_paid) AS total_paid,
10469         SUM(total_owed) AS total_owed,
10470         SUM(balance_owed) AS balance_owed
10471     FROM  money.materialized_billable_xact_summary
10472     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10473     GROUP BY usr;
10474
10475 CREATE OR REPLACE VIEW money.usr_summary AS
10476     SELECT 
10477         usr, 
10478         sum(total_paid) AS total_paid, 
10479         sum(total_owed) AS total_owed, 
10480         sum(balance_owed) AS balance_owed
10481     FROM money.materialized_billable_xact_summary
10482     GROUP BY usr;
10483
10484 CREATE OR REPLACE VIEW money.open_usr_summary AS
10485     SELECT 
10486         usr, 
10487         sum(total_paid) AS total_paid, 
10488         sum(total_owed) AS total_owed, 
10489         sum(balance_owed) AS balance_owed
10490     FROM money.materialized_billable_xact_summary
10491     WHERE xact_finish IS NULL
10492     GROUP BY usr;
10493
10494 -- 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;
10495
10496 CREATE TABLE config.biblio_fingerprint (
10497         id                      SERIAL  PRIMARY KEY,
10498         name            TEXT    NOT NULL, 
10499         xpath           TEXT    NOT NULL,
10500     first_word  BOOL    NOT NULL DEFAULT FALSE,
10501         format          TEXT    NOT NULL DEFAULT 'marcxml'
10502 );
10503
10504 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10505     VALUES (
10506         'Title',
10507         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10508             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10509             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10510             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10511             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10512         'marcxml'
10513     );
10514
10515 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10516     VALUES (
10517         'Author',
10518         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10519             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10520             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10521             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10522             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10523         'marcxml',
10524         TRUE
10525     );
10526
10527 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10528 DECLARE
10529     qual        INT;
10530     ldr         TEXT;
10531     tval        TEXT;
10532     tval_rec    RECORD;
10533     bval        TEXT;
10534     bval_rec    RECORD;
10535     type_map    RECORD;
10536     ff_pos      RECORD;
10537     ff_tag_data TEXT;
10538 BEGIN
10539
10540     IF marc IS NULL OR marc = '' THEN
10541         RETURN NULL;
10542     END IF;
10543
10544     -- First, the count of tags
10545     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10546
10547     -- now go through a bunch of pain to get the record type
10548     IF best_type IS NOT NULL THEN
10549         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10550
10551         IF ldr IS NOT NULL THEN
10552             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10553             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10554
10555
10556             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10557             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10558
10559             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10560
10561             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10562
10563             IF type_map.code IS NOT NULL THEN
10564                 IF best_type = type_map.code THEN
10565                     qual := qual + qual / 2;
10566                 END IF;
10567
10568                 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
10569                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10570                     IF ff_tag_data = best_lang THEN
10571                             qual := qual + 100;
10572                     END IF;
10573                 END LOOP;
10574             END IF;
10575         END IF;
10576     END IF;
10577
10578     -- Now look for some quality metrics
10579     -- DCL record?
10580     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10581         qual := qual + 10;
10582     END IF;
10583
10584     -- From OCLC?
10585     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10586         qual := qual + 10;
10587     END IF;
10588
10589     RETURN qual;
10590
10591 END;
10592 $func$ LANGUAGE PLPGSQL;
10593
10594 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10595 DECLARE
10596     idx     config.biblio_fingerprint%ROWTYPE;
10597     xfrm        config.xml_transform%ROWTYPE;
10598     prev_xfrm   TEXT;
10599     transformed_xml TEXT;
10600     xml_node    TEXT;
10601     xml_node_list   TEXT[];
10602     raw_text    TEXT;
10603     output_text TEXT := '';
10604 BEGIN
10605
10606     IF marc IS NULL OR marc = '' THEN
10607         RETURN NULL;
10608     END IF;
10609
10610     -- Loop over the indexing entries
10611     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10612
10613         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10614
10615         -- See if we can skip the XSLT ... it's expensive
10616         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10617             -- Can't skip the transform
10618             IF xfrm.xslt <> '---' THEN
10619                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10620             ELSE
10621                 transformed_xml := marc;
10622             END IF;
10623
10624             prev_xfrm := xfrm.name;
10625         END IF;
10626
10627         raw_text := COALESCE(
10628             naco_normalize(
10629                 ARRAY_TO_STRING(
10630                     oils_xpath(
10631                         '//text()',
10632                         (oils_xpath(
10633                             idx.xpath,
10634                             transformed_xml,
10635                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10636                         ))[1]
10637                     ),
10638                     ''
10639                 )
10640             ),
10641             ''
10642         );
10643
10644         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10645         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10646
10647         IF idx.first_word IS TRUE THEN
10648             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10649         END IF;
10650
10651         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10652
10653     END LOOP;
10654
10655     RETURN output_text;
10656
10657 END;
10658 $func$ LANGUAGE PLPGSQL;
10659
10660 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10661 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10662 BEGIN
10663
10664     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10665
10666     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10667         RETURN NEW;
10668     END IF;
10669
10670     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10671     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10672
10673     RETURN NEW;
10674
10675 END;
10676 $func$ LANGUAGE PLPGSQL;
10677
10678 CREATE TABLE config.internal_flag (
10679     name    TEXT    PRIMARY KEY,
10680     value   TEXT,
10681     enabled BOOL    NOT NULL DEFAULT FALSE
10682 );
10683 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10684 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10685 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10686 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10687 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10688 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10689 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10690 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10691 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10692 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10693
10694 CREATE TABLE authority.bib_linking (
10695     id          BIGSERIAL   PRIMARY KEY,
10696     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10697     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10698 );
10699 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10700 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10701
10702 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10703     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10704 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10705
10706 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10707     DELETE FROM authority.bib_linking WHERE bib = $1;
10708     INSERT INTO authority.bib_linking (bib, authority)
10709         SELECT  y.bib,
10710                 y.authority
10711           FROM (    SELECT  DISTINCT $1 AS bib,
10712                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10713                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10714                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10715                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10716     SELECT $1;
10717 $func$ LANGUAGE SQL;
10718
10719 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10720 BEGIN
10721     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10722     IF NOT FOUND THEN
10723         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10724     END IF;
10725     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)
10726         SELECT  bib_id,
10727                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10728                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10729                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10730                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10731                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10732                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10733                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10734                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10735                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10736                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10737                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10738                 (   SELECT  v.value
10739                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10740                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10741                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10742                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10743                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
10744                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
10745
10746     RETURN;
10747 END;
10748 $func$ LANGUAGE PLPGSQL;
10749
10750 CREATE TABLE config.metabib_class (
10751     name    TEXT    PRIMARY KEY,
10752     label   TEXT    NOT NULL UNIQUE
10753 );
10754
10755 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10756 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10757 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10758 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10759 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10760
10761 CREATE TABLE metabib.facet_entry (
10762         id              BIGSERIAL       PRIMARY KEY,
10763         source          BIGINT          NOT NULL,
10764         field           INT             NOT NULL,
10765         value           TEXT            NOT NULL
10766 );
10767
10768 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10769 DECLARE
10770     fclass          RECORD;
10771     ind_data        metabib.field_entry_template%ROWTYPE;
10772 BEGIN
10773     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10774     IF NOT FOUND THEN
10775         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10776             -- RAISE NOTICE 'Emptying out %', fclass.name;
10777             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10778         END LOOP;
10779         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10780     END IF;
10781
10782     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10783         IF ind_data.field < 0 THEN
10784             ind_data.field = -1 * ind_data.field;
10785             INSERT INTO metabib.facet_entry (field, source, value)
10786                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10787         ELSE
10788             EXECUTE $$
10789                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10790                     VALUES ($$ ||
10791                         quote_literal(ind_data.field) || $$, $$ ||
10792                         quote_literal(ind_data.source) || $$, $$ ||
10793                         quote_literal(ind_data.value) ||
10794                     $$);$$;
10795         END IF;
10796
10797     END LOOP;
10798
10799     RETURN;
10800 END;
10801 $func$ LANGUAGE PLPGSQL;
10802
10803 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
10804 DECLARE
10805     uris            TEXT[];
10806     uri_xml         TEXT;
10807     uri_label       TEXT;
10808     uri_href        TEXT;
10809     uri_use         TEXT;
10810     uri_owner       TEXT;
10811     uri_owner_id    INT;
10812     uri_id          INT;
10813     uri_cn_id       INT;
10814     uri_map_id      INT;
10815 BEGIN
10816
10817     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
10818     IF ARRAY_UPPER(uris,1) > 0 THEN
10819         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
10820             -- First we pull info out of the 856
10821             uri_xml     := uris[i];
10822
10823             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
10824             CONTINUE WHEN uri_href IS NULL;
10825
10826             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10827             CONTINUE WHEN uri_label IS NULL;
10828
10829             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
10830             CONTINUE WHEN uri_owner IS NULL;
10831
10832             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()',uri_xml))[1];
10833
10834             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
10835
10836             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
10837             CONTINUE WHEN NOT FOUND;
10838
10839             -- now we look for a matching uri
10840             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10841             IF NOT FOUND THEN -- create one
10842                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
10843                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10844             END IF;
10845
10846             -- we need a call number to link through
10847             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;
10848             IF NOT FOUND THEN
10849                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
10850                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
10851                 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;
10852             END IF;
10853
10854             -- now, link them if they're not already
10855             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
10856             IF NOT FOUND THEN
10857                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
10858             END IF;
10859
10860         END LOOP;
10861     END IF;
10862
10863     RETURN;
10864 END;
10865 $func$ LANGUAGE PLPGSQL;
10866
10867 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
10868 DECLARE
10869     source_count    INT;
10870     old_mr          BIGINT;
10871     tmp_mr          metabib.metarecord%ROWTYPE;
10872     deleted_mrs     BIGINT[];
10873 BEGIN
10874
10875     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
10876
10877     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
10878
10879         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
10880             old_mr := tmp_mr.id;
10881         ELSE
10882             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
10883             IF source_count = 0 THEN -- No other records
10884                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
10885                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
10886             END IF;
10887         END IF;
10888
10889     END LOOP;
10890
10891     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
10892         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
10893         IF old_mr IS NULL THEN -- nope, create one and grab its id
10894             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
10895             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
10896         ELSE -- indeed there is. update it with a null cache and recalcualated master record
10897             UPDATE  metabib.metarecord
10898               SET   mods = NULL,
10899                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
10900               WHERE id = old_mr;
10901         END IF;
10902     ELSE -- there was one we already attached to, update its mods cache and master_record
10903         UPDATE  metabib.metarecord
10904           SET   mods = NULL,
10905                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
10906           WHERE id = old_mr;
10907     END IF;
10908
10909     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
10910
10911     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
10912         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
10913     END IF;
10914
10915     RETURN old_mr;
10916
10917 END;
10918 $func$ LANGUAGE PLPGSQL;
10919
10920 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
10921 BEGIN
10922     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10923     IF NOT FOUND THEN
10924         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
10925     END IF;
10926     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
10927         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
10928
10929     RETURN;
10930 END;
10931 $func$ LANGUAGE PLPGSQL;
10932
10933 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
10934 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
10935 BEGIN
10936
10937     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
10938         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
10939         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
10940         RETURN NEW; -- and we're done
10941     END IF;
10942
10943     IF TG_OP = 'UPDATE' THEN -- re-ingest?
10944         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
10945
10946         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
10947             RETURN NEW;
10948         END IF;
10949     END IF;
10950
10951     -- Record authority linking
10952     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
10953     IF NOT FOUND THEN
10954         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
10955     END IF;
10956
10957     -- Flatten and insert the mfr data
10958     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
10959     IF NOT FOUND THEN
10960         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
10961         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
10962         IF NOT FOUND THEN
10963             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
10964         END IF;
10965     END IF;
10966
10967     -- Gather and insert the field entry data
10968     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
10969
10970     -- Located URI magic
10971     IF TG_OP = 'INSERT' THEN
10972         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
10973         IF NOT FOUND THEN
10974             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
10975         END IF;
10976     ELSE
10977         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
10978         IF NOT FOUND THEN
10979             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
10980         END IF;
10981     END IF;
10982
10983     -- (re)map metarecord-bib linking
10984     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
10985         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
10986         IF NOT FOUND THEN
10987             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
10988         END IF;
10989     ELSE -- we're doing an update, and we're not deleted, remap
10990         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
10991         IF NOT FOUND THEN
10992             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
10993         END IF;
10994     END IF;
10995
10996     RETURN NEW;
10997 END;
10998 $func$ LANGUAGE PLPGSQL;
10999
11000 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
11001 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 ();
11002
11003 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
11004
11005 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
11006 DECLARE
11007     xpath_list  TEXT[];
11008     select_list TEXT[];
11009     where_list  TEXT[];
11010     q           TEXT;
11011     out_record  RECORD;
11012     empty_test  RECORD;
11013 BEGIN
11014     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
11015  
11016     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
11017  
11018     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
11019         IF xpath_list[i] = 'null()' THEN
11020             select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
11021         ELSE
11022             select_list := ARRAY_APPEND(
11023                 select_list,
11024                 $sel$
11025                 EXPLODE_ARRAY(
11026                     COALESCE(
11027                         NULLIF(
11028                             oils_xpath(
11029                                 $sel$ ||
11030                                     quote_literal(
11031                                         CASE
11032                                             WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
11033                                             ELSE xpath_list[i] || '//text()'
11034                                         END
11035                                     ) ||
11036                                 $sel$,
11037                                 $sel$ || document_field || $sel$
11038                             ),
11039                            '{}'::TEXT[]
11040                         ),
11041                         '{NULL}'::TEXT[]
11042                     )
11043                 ) AS c_$sel$ || i
11044             );
11045             where_list := ARRAY_APPEND(
11046                 where_list,
11047                 'c_' || i || ' IS NOT NULL'
11048             );
11049         END IF;
11050     END LOOP;
11051  
11052     q := $q$
11053 SELECT * FROM (
11054     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11055 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
11056     -- RAISE NOTICE 'query: %', q;
11057  
11058     FOR out_record IN EXECUTE q LOOP
11059         RETURN NEXT out_record;
11060     END LOOP;
11061  
11062     RETURN;
11063 END;
11064 $func$ LANGUAGE PLPGSQL IMMUTABLE;
11065
11066 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11067 DECLARE
11068
11069     owning_lib      TEXT;
11070     circ_lib        TEXT;
11071     call_number     TEXT;
11072     copy_number     TEXT;
11073     status          TEXT;
11074     location        TEXT;
11075     circulate       TEXT;
11076     deposit         TEXT;
11077     deposit_amount  TEXT;
11078     ref             TEXT;
11079     holdable        TEXT;
11080     price           TEXT;
11081     barcode         TEXT;
11082     circ_modifier   TEXT;
11083     circ_as_type    TEXT;
11084     alert_message   TEXT;
11085     opac_visible    TEXT;
11086     pub_note        TEXT;
11087     priv_note       TEXT;
11088
11089     attr_def        RECORD;
11090     tmp_attr_set    RECORD;
11091     attr_set        vandelay.import_item%ROWTYPE;
11092
11093     xpath           TEXT;
11094
11095 BEGIN
11096
11097     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11098
11099     IF FOUND THEN
11100
11101         attr_set.definition := attr_def.id; 
11102     
11103         -- Build the combined XPath
11104     
11105         owning_lib :=
11106             CASE
11107                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11108                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11109                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11110             END;
11111     
11112         circ_lib :=
11113             CASE
11114                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11115                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11116                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11117             END;
11118     
11119         call_number :=
11120             CASE
11121                 WHEN attr_def.call_number IS NULL THEN 'null()'
11122                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11123                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11124             END;
11125     
11126         copy_number :=
11127             CASE
11128                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11129                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11130                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11131             END;
11132     
11133         status :=
11134             CASE
11135                 WHEN attr_def.status IS NULL THEN 'null()'
11136                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11137                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11138             END;
11139     
11140         location :=
11141             CASE
11142                 WHEN attr_def.location IS NULL THEN 'null()'
11143                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11144                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11145             END;
11146     
11147         circulate :=
11148             CASE
11149                 WHEN attr_def.circulate IS NULL THEN 'null()'
11150                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11151                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11152             END;
11153     
11154         deposit :=
11155             CASE
11156                 WHEN attr_def.deposit IS NULL THEN 'null()'
11157                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11158                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11159             END;
11160     
11161         deposit_amount :=
11162             CASE
11163                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11164                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11165                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11166             END;
11167     
11168         ref :=
11169             CASE
11170                 WHEN attr_def.ref IS NULL THEN 'null()'
11171                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11172                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11173             END;
11174     
11175         holdable :=
11176             CASE
11177                 WHEN attr_def.holdable IS NULL THEN 'null()'
11178                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11179                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11180             END;
11181     
11182         price :=
11183             CASE
11184                 WHEN attr_def.price IS NULL THEN 'null()'
11185                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11186                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11187             END;
11188     
11189         barcode :=
11190             CASE
11191                 WHEN attr_def.barcode IS NULL THEN 'null()'
11192                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11193                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11194             END;
11195     
11196         circ_modifier :=
11197             CASE
11198                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11199                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11200                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11201             END;
11202     
11203         circ_as_type :=
11204             CASE
11205                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11206                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11207                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11208             END;
11209     
11210         alert_message :=
11211             CASE
11212                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11213                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11214                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11215             END;
11216     
11217         opac_visible :=
11218             CASE
11219                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11220                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11221                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11222             END;
11223
11224         pub_note :=
11225             CASE
11226                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11227                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11228                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11229             END;
11230         priv_note :=
11231             CASE
11232                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11233                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11234                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11235             END;
11236     
11237     
11238         xpath := 
11239             owning_lib      || '|' || 
11240             circ_lib        || '|' || 
11241             call_number     || '|' || 
11242             copy_number     || '|' || 
11243             status          || '|' || 
11244             location        || '|' || 
11245             circulate       || '|' || 
11246             deposit         || '|' || 
11247             deposit_amount  || '|' || 
11248             ref             || '|' || 
11249             holdable        || '|' || 
11250             price           || '|' || 
11251             barcode         || '|' || 
11252             circ_modifier   || '|' || 
11253             circ_as_type    || '|' || 
11254             alert_message   || '|' || 
11255             pub_note        || '|' || 
11256             priv_note       || '|' || 
11257             opac_visible;
11258
11259         -- RAISE NOTICE 'XPath: %', xpath;
11260         
11261         FOR tmp_attr_set IN
11262                 SELECT  *
11263                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11264                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11265                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11266                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11267         LOOP
11268     
11269             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11270             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11271
11272             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11273             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11274     
11275             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11276             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11277             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11278     
11279             SELECT  id INTO attr_set.location
11280               FROM  asset.copy_location
11281               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11282                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11283     
11284             attr_set.circulate      :=
11285                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11286                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11287
11288             attr_set.deposit        :=
11289                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11290                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11291
11292             attr_set.holdable       :=
11293                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11294                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11295
11296             attr_set.opac_visible   :=
11297                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11298                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11299
11300             attr_set.ref            :=
11301                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11302                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11303     
11304             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11305             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11306             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11307     
11308             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11309             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11310             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11311             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11312             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11313             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11314             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11315             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11316     
11317             RETURN NEXT attr_set;
11318     
11319         END LOOP;
11320     
11321     END IF;
11322
11323     RETURN;
11324
11325 END;
11326 $$ LANGUAGE PLPGSQL;
11327
11328 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11329 DECLARE
11330     attr_def    BIGINT;
11331     item_data   vandelay.import_item%ROWTYPE;
11332 BEGIN
11333
11334     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11335
11336     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11337         INSERT INTO vandelay.import_item (
11338             record,
11339             definition,
11340             owning_lib,
11341             circ_lib,
11342             call_number,
11343             copy_number,
11344             status,
11345             location,
11346             circulate,
11347             deposit,
11348             deposit_amount,
11349             ref,
11350             holdable,
11351             price,
11352             barcode,
11353             circ_modifier,
11354             circ_as_type,
11355             alert_message,
11356             pub_note,
11357             priv_note,
11358             opac_visible
11359         ) VALUES (
11360             NEW.id,
11361             item_data.definition,
11362             item_data.owning_lib,
11363             item_data.circ_lib,
11364             item_data.call_number,
11365             item_data.copy_number,
11366             item_data.status,
11367             item_data.location,
11368             item_data.circulate,
11369             item_data.deposit,
11370             item_data.deposit_amount,
11371             item_data.ref,
11372             item_data.holdable,
11373             item_data.price,
11374             item_data.barcode,
11375             item_data.circ_modifier,
11376             item_data.circ_as_type,
11377             item_data.alert_message,
11378             item_data.pub_note,
11379             item_data.priv_note,
11380             item_data.opac_visible
11381         );
11382     END LOOP;
11383
11384     RETURN NULL;
11385 END;
11386 $func$ LANGUAGE PLPGSQL;
11387
11388 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11389 BEGIN
11390     EXECUTE $$
11391         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11392     $$;
11393         RETURN TRUE;
11394 END;
11395 $creator$ LANGUAGE 'plpgsql';
11396
11397 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11398 BEGIN
11399     EXECUTE $$
11400         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11401             audit_id    BIGINT                          PRIMARY KEY,
11402             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11403             audit_action        TEXT                            NOT NULL,
11404             LIKE $$ || sch || $$.$$ || tbl || $$
11405         );
11406     $$;
11407         RETURN TRUE;
11408 END;
11409 $creator$ LANGUAGE 'plpgsql';
11410
11411 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11412 BEGIN
11413     EXECUTE $$
11414         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11415         RETURNS TRIGGER AS $func$
11416         BEGIN
11417             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11418                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11419                     now(),
11420                     SUBSTR(TG_OP,1,1),
11421                     OLD.*;
11422             RETURN NULL;
11423         END;
11424         $func$ LANGUAGE 'plpgsql';
11425     $$;
11426         RETURN TRUE;
11427 END;
11428 $creator$ LANGUAGE 'plpgsql';
11429
11430 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11431 BEGIN
11432     EXECUTE $$
11433         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11434             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11435             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11436     $$;
11437         RETURN TRUE;
11438 END;
11439 $creator$ LANGUAGE 'plpgsql';
11440
11441 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11442 BEGIN
11443     EXECUTE $$
11444         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11445             SELECT      -1, now() as audit_time, '-' as audit_action, *
11446               FROM      $$ || sch || $$.$$ || tbl || $$
11447                 UNION ALL
11448             SELECT      *
11449               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11450     $$;
11451         RETURN TRUE;
11452 END;
11453 $creator$ LANGUAGE 'plpgsql';
11454
11455 -- The main event
11456
11457 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11458 BEGIN
11459     PERFORM acq.create_acq_seq(sch, tbl);
11460     PERFORM acq.create_acq_history(sch, tbl);
11461     PERFORM acq.create_acq_func(sch, tbl);
11462     PERFORM acq.create_acq_update_trigger(sch, tbl);
11463     PERFORM acq.create_acq_lifecycle(sch, tbl);
11464     RETURN TRUE;
11465 END;
11466 $creator$ LANGUAGE 'plpgsql';
11467
11468 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11469
11470 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11471     SELECT  fund.id AS fund,
11472             fund_debit.encumbrance AS encumbrance,
11473             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11474       FROM acq.fund AS fund
11475                         LEFT JOIN acq.fund_debit AS fund_debit
11476                                 ON ( fund.id = fund_debit.fund )
11477       GROUP BY 1,2;
11478
11479 CREATE TABLE acq.debit_attribution (
11480         id                     INT         NOT NULL PRIMARY KEY,
11481         fund_debit             INT         NOT NULL
11482                                            REFERENCES acq.fund_debit
11483                                            DEFERRABLE INITIALLY DEFERRED,
11484     debit_amount           NUMERIC     NOT NULL,
11485         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11486                                            DEFERRABLE INITIALLY DEFERRED,
11487     credit_amount          NUMERIC
11488 );
11489
11490 CREATE INDEX acq_attribution_debit_idx
11491         ON acq.debit_attribution( fund_debit );
11492
11493 CREATE INDEX acq_attribution_credit_idx
11494         ON acq.debit_attribution( funding_source_credit );
11495
11496 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11497 /*
11498 Function to attribute expenditures and encumbrances to funding source credits,
11499 and thereby to funding sources.
11500
11501 Read the debits in chonological order, attributing each one to one or
11502 more funding source credits.  Constraints:
11503
11504 1. Don't attribute more to a credit than the amount of the credit.
11505
11506 2. For a given fund, don't attribute more to a funding source than the
11507 source has allocated to that fund.
11508
11509 3. Attribute debits to credits with deadlines before attributing them to
11510 credits without deadlines.  Otherwise attribute to the earliest credits
11511 first, based on the deadline date when present, or on the effective date
11512 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11513 This ordering is defined by an ORDER BY clause on the view
11514 acq.ordered_funding_source_credit.
11515
11516 Start by truncating the table acq.debit_attribution.  Then insert a row
11517 into that table for each attribution.  If a debit cannot be fully
11518 attributed, insert a row for the unattributable balance, with the 
11519 funding_source_credit and credit_amount columns NULL.
11520 */
11521 DECLARE
11522         curr_fund_source_bal RECORD;
11523         seqno                INT;     -- sequence num for credits applicable to a fund
11524         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11525         fc                   RECORD;  -- used for loading t_fund_credit table
11526         sc                   RECORD;  -- used for loading t_fund_credit table
11527         --
11528         -- Used exclusively in the main loop:
11529         --
11530         deb                 RECORD;   -- current row from acq.fund_debit table
11531         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11532         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11533         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11534         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11535         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11536         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11537         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11538         attrib_count        INT;      -- populates id of acq.debit_attribution
11539 BEGIN
11540         --
11541         -- Load a temporary table.  For each combination of fund and funding source,
11542         -- load an entry with the total amount allocated to that fund by that source.
11543         -- This sum may reflect transfers as well as original allocations.  We will
11544         -- reduce this balance whenever we attribute debits to it.
11545         --
11546         CREATE TEMP TABLE t_fund_source_bal
11547         ON COMMIT DROP AS
11548                 SELECT
11549                         fund AS fund,
11550                         funding_source AS source,
11551                         sum( amount ) AS balance
11552                 FROM
11553                         acq.fund_allocation
11554                 GROUP BY
11555                         fund,
11556                         funding_source
11557                 HAVING
11558                         sum( amount ) > 0;
11559         --
11560         CREATE INDEX t_fund_source_bal_idx
11561                 ON t_fund_source_bal( fund, source );
11562         -------------------------------------------------------------------------------
11563         --
11564         -- Load another temporary table.  For each fund, load zero or more
11565         -- funding source credits from which that fund can get money.
11566         --
11567         CREATE TEMP TABLE t_fund_credit (
11568                 fund        INT,
11569                 seq         INT,
11570                 credit      INT
11571         ) ON COMMIT DROP;
11572         --
11573         FOR fc IN
11574                 SELECT DISTINCT fund
11575                 FROM acq.fund_allocation
11576                 ORDER BY fund
11577         LOOP                  -- Loop over the funds
11578                 seqno := 1;
11579                 FOR sc IN
11580                         SELECT
11581                                 ofsc.id
11582                         FROM
11583                                 acq.ordered_funding_source_credit AS ofsc
11584                         WHERE
11585                                 ofsc.funding_source IN
11586                                 (
11587                                         SELECT funding_source
11588                                         FROM acq.fund_allocation
11589                                         WHERE fund = fc.fund
11590                                 )
11591                 ORDER BY
11592                     ofsc.sort_priority,
11593                     ofsc.sort_date,
11594                     ofsc.id
11595                 LOOP                        -- Add each credit to the list
11596                         INSERT INTO t_fund_credit (
11597                                 fund,
11598                                 seq,
11599                                 credit
11600                         ) VALUES (
11601                                 fc.fund,
11602                                 seqno,
11603                                 sc.id
11604                         );
11605                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11606                         seqno := seqno + 1;
11607                 END LOOP;     -- Loop over credits for a given fund
11608         END LOOP;         -- Loop over funds
11609         --
11610         CREATE INDEX t_fund_credit_idx
11611                 ON t_fund_credit( fund, seq );
11612         -------------------------------------------------------------------------------
11613         --
11614         -- Load yet another temporary table.  This one is a list of funding source
11615         -- credits, with their balances.  We shall reduce those balances as we
11616         -- attribute debits to them.
11617         --
11618         CREATE TEMP TABLE t_credit
11619         ON COMMIT DROP AS
11620         SELECT
11621             fsc.id AS credit,
11622             fsc.funding_source AS source,
11623             fsc.amount AS balance,
11624             fs.currency_type AS currency_type
11625         FROM
11626             acq.funding_source_credit AS fsc,
11627             acq.funding_source fs
11628         WHERE
11629             fsc.funding_source = fs.id
11630                         AND fsc.amount > 0;
11631         --
11632         CREATE INDEX t_credit_idx
11633                 ON t_credit( credit );
11634         --
11635         -------------------------------------------------------------------------------
11636         --
11637         -- Now that we have loaded the lookup tables: loop through the debits,
11638         -- attributing each one to one or more funding source credits.
11639         -- 
11640         truncate table acq.debit_attribution;
11641         --
11642         attrib_count := 0;
11643         FOR deb in
11644                 SELECT
11645                         fd.id,
11646                         fd.fund,
11647                         fd.amount,
11648                         f.currency_type,
11649                         fd.encumbrance
11650                 FROM
11651                         acq.fund_debit fd,
11652                         acq.fund f
11653                 WHERE
11654                         fd.fund = f.id
11655                 ORDER BY
11656                         fd.id
11657         LOOP
11658                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11659                 --
11660                 debit_balance := deb.amount;
11661                 --
11662                 -- Loop over the funding source credits that are eligible
11663                 -- to pay for this debit
11664                 --
11665                 FOR fund_credit IN
11666                         SELECT
11667                                 credit
11668                         FROM
11669                                 t_fund_credit
11670                         WHERE
11671                                 fund = deb.fund
11672                         ORDER BY
11673                                 seq
11674                 LOOP
11675                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11676                         --
11677                         -- Look up the balance for this credit.  If it's zero, then
11678                         -- it's not useful, so treat it as if you didn't find it.
11679                         -- (Actually there shouldn't be any zero balances in the table,
11680                         -- but we check just to make sure.)
11681                         --
11682                         SELECT *
11683                         INTO curr_credit_bal
11684                         FROM t_credit
11685                         WHERE
11686                                 credit = fund_credit.credit
11687                                 AND balance > 0;
11688                         --
11689                         IF curr_credit_bal IS NULL THEN
11690                                 --
11691                                 -- This credit is exhausted; try the next one.
11692                                 --
11693                                 CONTINUE;
11694                         END IF;
11695                         --
11696                         --
11697                         -- At this point we have an applicable credit with some money left.
11698                         -- Now see if the relevant funding_source has any money left.
11699                         --
11700                         -- Look up the balance of the allocation for this combination of
11701                         -- fund and source.  If you find such an entry, but it has a zero
11702                         -- balance, then it's not useful, so treat it as unfound.
11703                         -- (Actually there shouldn't be any zero balances in the table,
11704                         -- but we check just to make sure.)
11705                         --
11706                         SELECT *
11707                         INTO curr_fund_source_bal
11708                         FROM t_fund_source_bal
11709                         WHERE
11710                                 fund = deb.fund
11711                                 AND source = curr_credit_bal.source
11712                                 AND balance > 0;
11713                         --
11714                         IF curr_fund_source_bal IS NULL THEN
11715                                 --
11716                                 -- This fund/source doesn't exist or is already exhausted,
11717                                 -- so we can't use this credit.  Go on to the next one.
11718                                 --
11719                                 CONTINUE;
11720                         END IF;
11721                         --
11722                         -- Convert the available balances to the currency of the fund
11723                         --
11724                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11725                                 curr_credit_bal.currency_type, deb.currency_type );
11726                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11727                                 curr_credit_bal.currency_type, deb.currency_type );
11728                         --
11729                         -- Determine how much we can attribute to this credit: the minimum
11730                         -- of the debit amount, the fund/source balance, and the
11731                         -- credit balance
11732                         --
11733                         --RAISE NOTICE '   deb bal %', debit_balance;
11734                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11735                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11736                         --
11737                         conv_attr_amount := NULL;
11738                         attr_amount := debit_balance;
11739                         --
11740                         IF attr_amount > conv_alloc_balance THEN
11741                                 attr_amount := conv_alloc_balance;
11742                                 conv_attr_amount := curr_fund_source_bal.balance;
11743                         END IF;
11744                         IF attr_amount > conv_cred_balance THEN
11745                                 attr_amount := conv_cred_balance;
11746                                 conv_attr_amount := curr_credit_bal.balance;
11747                         END IF;
11748                         --
11749                         -- If we're attributing all of one of the balances, then that's how
11750                         -- much we will deduct from the balances, and we already captured
11751                         -- that amount above.  Otherwise we must convert the amount of the
11752                         -- attribution from the currency of the fund back to the currency of
11753                         -- the funding source.
11754                         --
11755                         IF conv_attr_amount IS NULL THEN
11756                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11757                                         deb.currency_type, curr_credit_bal.currency_type );
11758                         END IF;
11759                         --
11760                         -- Insert a row to record the attribution
11761                         --
11762                         attrib_count := attrib_count + 1;
11763                         INSERT INTO acq.debit_attribution (
11764                                 id,
11765                                 fund_debit,
11766                                 debit_amount,
11767                                 funding_source_credit,
11768                                 credit_amount
11769                         ) VALUES (
11770                                 attrib_count,
11771                                 deb.id,
11772                                 attr_amount,
11773                                 curr_credit_bal.credit,
11774                                 conv_attr_amount
11775                         );
11776                         --
11777                         -- Subtract the attributed amount from the various balances
11778                         --
11779                         debit_balance := debit_balance - attr_amount;
11780                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11781                         --
11782                         IF curr_fund_source_bal.balance <= 0 THEN
11783                                 --
11784                                 -- This allocation is exhausted.  Delete it so
11785                                 -- that we don't waste time looking at it again.
11786                                 --
11787                                 DELETE FROM t_fund_source_bal
11788                                 WHERE
11789                                         fund = curr_fund_source_bal.fund
11790                                         AND source = curr_fund_source_bal.source;
11791                         ELSE
11792                                 UPDATE t_fund_source_bal
11793                                 SET balance = balance - conv_attr_amount
11794                                 WHERE
11795                                         fund = curr_fund_source_bal.fund
11796                                         AND source = curr_fund_source_bal.source;
11797                         END IF;
11798                         --
11799                         IF curr_credit_bal.balance <= 0 THEN
11800                                 --
11801                                 -- This funding source credit is exhausted.  Delete it
11802                                 -- so that we don't waste time looking at it again.
11803                                 --
11804                                 --DELETE FROM t_credit
11805                                 --WHERE
11806                                 --      credit = curr_credit_bal.credit;
11807                                 --
11808                                 DELETE FROM t_fund_credit
11809                                 WHERE
11810                                         credit = curr_credit_bal.credit;
11811                         ELSE
11812                                 UPDATE t_credit
11813                                 SET balance = curr_credit_bal.balance
11814                                 WHERE
11815                                         credit = curr_credit_bal.credit;
11816                         END IF;
11817                         --
11818                         -- Are we done with this debit yet?
11819                         --
11820                         IF debit_balance <= 0 THEN
11821                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
11822                         END IF;
11823                 END LOOP;       -- End loop over credits
11824                 --
11825                 IF debit_balance <> 0 THEN
11826                         --
11827                         -- We weren't able to attribute this debit, or at least not
11828                         -- all of it.  Insert a row for the unattributed balance.
11829                         --
11830                         attrib_count := attrib_count + 1;
11831                         INSERT INTO acq.debit_attribution (
11832                                 id,
11833                                 fund_debit,
11834                                 debit_amount,
11835                                 funding_source_credit,
11836                                 credit_amount
11837                         ) VALUES (
11838                                 attrib_count,
11839                                 deb.id,
11840                                 debit_balance,
11841                                 NULL,
11842                                 NULL
11843                         );
11844                 END IF;
11845         END LOOP;   -- End of loop over debits
11846 END;
11847 $$ LANGUAGE 'plpgsql';
11848
11849 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
11850 DECLARE
11851     query TEXT;
11852     output TEXT;
11853 BEGIN
11854     query := $q$
11855         SELECT  regexp_replace(
11856                     oils_xpath_string(
11857                         $q$ || quote_literal($3) || $q$,
11858                         marc,
11859                         ' '
11860                     ),
11861                     $q$ || quote_literal($4) || $q$,
11862                     '',
11863                     'g')
11864           FROM  $q$ || $1 || $q$
11865           WHERE id = $q$ || $2;
11866
11867     EXECUTE query INTO output;
11868
11869     -- RAISE NOTICE 'query: %, output; %', query, output;
11870
11871     RETURN output;
11872 END;
11873 $$ LANGUAGE PLPGSQL IMMUTABLE;
11874
11875 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
11876     SELECT extract_marc_field($1,$2,$3,'');
11877 $$ LANGUAGE SQL IMMUTABLE;
11878
11879 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
11880 DECLARE
11881     moved_objects INT := 0;
11882     source_cn     asset.call_number%ROWTYPE;
11883     target_cn     asset.call_number%ROWTYPE;
11884     metarec       metabib.metarecord%ROWTYPE;
11885     hold          action.hold_request%ROWTYPE;
11886     ser_rec       serial.record_entry%ROWTYPE;
11887     uri_count     INT := 0;
11888     counter       INT := 0;
11889     uri_datafield TEXT;
11890     uri_text      TEXT := '';
11891 BEGIN
11892
11893     -- move any 856 entries on records that have at least one MARC-mapped URI entry
11894     SELECT  INTO uri_count COUNT(*)
11895       FROM  asset.uri_call_number_map m
11896             JOIN asset.call_number cn ON (m.call_number = cn.id)
11897       WHERE cn.record = source_record;
11898
11899     IF uri_count > 0 THEN
11900
11901         SELECT  COUNT(*) INTO counter
11902           FROM  oils_xpath_table(
11903                     'id',
11904                     'marc',
11905                     'biblio.record_entry',
11906                     '//*[@tag="856"]',
11907                     'id=' || source_record
11908                 ) as t(i int,c text);
11909
11910         FOR i IN 1 .. counter LOOP
11911             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
11912                         ' tag="856"' || 
11913                         ' ind1="' || FIRST(ind1) || '"'  || 
11914                         ' ind2="' || FIRST(ind2) || '">' || 
11915                         array_to_string(
11916                             array_accum(
11917                                 '<subfield code="' || subfield || '">' ||
11918                                 regexp_replace(
11919                                     regexp_replace(
11920                                         regexp_replace(data,'&','&amp;','g'),
11921                                         '>', '&gt;', 'g'
11922                                     ),
11923                                     '<', '&lt;', 'g'
11924                                 ) || '</subfield>'
11925                             ), ''
11926                         ) || '</datafield>' INTO uri_datafield
11927               FROM  oils_xpath_table(
11928                         'id',
11929                         'marc',
11930                         'biblio.record_entry',
11931                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
11932                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
11933                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
11934                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
11935                         'id=' || source_record
11936                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
11937
11938             uri_text := uri_text || uri_datafield;
11939         END LOOP;
11940
11941         IF uri_text <> '' THEN
11942             UPDATE  biblio.record_entry
11943               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
11944               WHERE id = target_record;
11945         END IF;
11946
11947     END IF;
11948
11949     -- Find and move metarecords to the target record
11950     SELECT  INTO metarec *
11951       FROM  metabib.metarecord
11952       WHERE master_record = source_record;
11953
11954     IF FOUND THEN
11955         UPDATE  metabib.metarecord
11956           SET   master_record = target_record,
11957             mods = NULL
11958           WHERE id = metarec.id;
11959
11960         moved_objects := moved_objects + 1;
11961     END IF;
11962
11963     -- Find call numbers attached to the source ...
11964     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
11965
11966         SELECT  INTO target_cn *
11967           FROM  asset.call_number
11968           WHERE label = source_cn.label
11969             AND owning_lib = source_cn.owning_lib
11970             AND record = target_record;
11971
11972         -- ... and if there's a conflicting one on the target ...
11973         IF FOUND THEN
11974
11975             -- ... move the copies to that, and ...
11976             UPDATE  asset.copy
11977               SET   call_number = target_cn.id
11978               WHERE call_number = source_cn.id;
11979
11980             -- ... move V holds to the move-target call number
11981             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
11982
11983                 UPDATE  action.hold_request
11984                   SET   target = target_cn.id
11985                   WHERE id = hold.id;
11986
11987                 moved_objects := moved_objects + 1;
11988             END LOOP;
11989
11990         -- ... if not ...
11991         ELSE
11992             -- ... just move the call number to the target record
11993             UPDATE  asset.call_number
11994               SET   record = target_record
11995               WHERE id = source_cn.id;
11996         END IF;
11997
11998         moved_objects := moved_objects + 1;
11999     END LOOP;
12000
12001     -- Find T holds targeting the source record ...
12002     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
12003
12004         -- ... and move them to the target record
12005         UPDATE  action.hold_request
12006           SET   target = target_record
12007           WHERE id = hold.id;
12008
12009         moved_objects := moved_objects + 1;
12010     END LOOP;
12011
12012     -- Find serial records targeting the source record ...
12013     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
12014         -- ... and move them to the target record
12015         UPDATE  serial.record_entry
12016           SET   record = target_record
12017           WHERE id = ser_rec.id;
12018
12019         moved_objects := moved_objects + 1;
12020     END LOOP;
12021
12022     -- Finally, "delete" the source record
12023     DELETE FROM biblio.record_entry WHERE id = source_record;
12024
12025     -- That's all, folks!
12026     RETURN moved_objects;
12027 END;
12028 $func$ LANGUAGE plpgsql;
12029
12030 CREATE OR REPLACE FUNCTION acq.transfer_fund(
12031         old_fund   IN INT,
12032         old_amount IN NUMERIC,     -- in currency of old fund
12033         new_fund   IN INT,
12034         new_amount IN NUMERIC,     -- in currency of new fund
12035         user_id    IN INT,
12036         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
12037         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
12038 ) RETURNS VOID AS $$
12039 /* -------------------------------------------------------------------------------
12040
12041 Function to transfer money from one fund to another.
12042
12043 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12044 negative amount for the old (losing) fund and a positive amount for the new
12045 (gaining) fund.  In some cases there may be more than one such pair of entries
12046 in order to pull the money from different funding sources, or more specifically
12047 from different funding source credits.  For each such pair there is also an
12048 entry in acq.fund_transfer.
12049
12050 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12051 choose a funding source for the transferred money to come from.  This choice
12052 must meet two constraints, so far as possible:
12053
12054 1. The amount transferred from a given funding source must not exceed the
12055 amount allocated to the old fund by the funding source.  To that end we
12056 compare the amount being transferred to the amount allocated.
12057
12058 2. We shouldn't transfer money that has already been spent or encumbered, as
12059 defined by the funding attribution process.  We attribute expenses to the
12060 oldest funding source credits first.  In order to avoid transferring that
12061 attributed money, we reverse the priority, transferring from the newest funding
12062 source credits first.  There can be no guarantee that this approach will
12063 avoid overcommitting a fund, but no other approach can do any better.
12064
12065 In this context the age of a funding source credit is defined by the
12066 deadline_date for credits with deadline_dates, and by the effective_date for
12067 credits without deadline_dates, with the proviso that credits with deadline_dates
12068 are all considered "older" than those without.
12069
12070 ----------
12071
12072 In the signature for this function, there is one last parameter commented out,
12073 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12074 driving the main loop has an OR clause commented out, which references the
12075 funding_source_in parameter.
12076
12077 If these lines are uncommented, this function will allow the user optionally to
12078 restrict a fund transfer to a specified funding source.  If the source
12079 parameter is left NULL, then there will be no such restriction.
12080
12081 ------------------------------------------------------------------------------- */ 
12082 DECLARE
12083         same_currency      BOOLEAN;
12084         currency_ratio     NUMERIC;
12085         old_fund_currency  TEXT;
12086         old_remaining      NUMERIC;  -- in currency of old fund
12087         new_fund_currency  TEXT;
12088         new_fund_active    BOOLEAN;
12089         new_remaining      NUMERIC;  -- in currency of new fund
12090         curr_old_amt       NUMERIC;  -- in currency of old fund
12091         curr_new_amt       NUMERIC;  -- in currency of new fund
12092         source_addition    NUMERIC;  -- in currency of funding source
12093         source_deduction   NUMERIC;  -- in currency of funding source
12094         orig_allocated_amt NUMERIC;  -- in currency of funding source
12095         allocated_amt      NUMERIC;  -- in currency of fund
12096         source             RECORD;
12097 BEGIN
12098         --
12099         -- Sanity checks
12100         --
12101         IF old_fund IS NULL THEN
12102                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12103         END IF;
12104         --
12105         IF old_amount IS NULL THEN
12106                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12107         END IF;
12108         --
12109         -- The new fund and its amount must be both NULL or both not NULL.
12110         --
12111         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12112                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12113         END IF;
12114         --
12115         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12116                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12117         END IF;
12118         --
12119         IF user_id IS NULL THEN
12120                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12121         END IF;
12122         --
12123         -- Initialize the amounts to be transferred, each denominated
12124         -- in the currency of its respective fund.  They will be
12125         -- reduced on each iteration of the loop.
12126         --
12127         old_remaining := old_amount;
12128         new_remaining := new_amount;
12129         --
12130         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12131         --      old_amount, old_fund, new_amount, new_fund;
12132         --
12133         -- Get the currency types of the old and new funds.
12134         --
12135         SELECT
12136                 currency_type
12137         INTO
12138                 old_fund_currency
12139         FROM
12140                 acq.fund
12141         WHERE
12142                 id = old_fund;
12143         --
12144         IF old_fund_currency IS NULL THEN
12145                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12146         END IF;
12147         --
12148         IF new_fund IS NOT NULL THEN
12149                 SELECT
12150                         currency_type,
12151                         active
12152                 INTO
12153                         new_fund_currency,
12154                         new_fund_active
12155                 FROM
12156                         acq.fund
12157                 WHERE
12158                         id = new_fund;
12159                 --
12160                 IF new_fund_currency IS NULL THEN
12161                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12162                 ELSIF NOT new_fund_active THEN
12163                         --
12164                         -- No point in putting money into a fund from whence you can't spend it
12165                         --
12166                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12167                 END IF;
12168                 --
12169                 IF new_amount = old_amount THEN
12170                         same_currency := true;
12171                         currency_ratio := 1;
12172                 ELSE
12173                         --
12174                         -- We'll have to translate currency between funds.  We presume that
12175                         -- the calling code has already applied an appropriate exchange rate,
12176                         -- so we'll apply the same conversion to each sub-transfer.
12177                         --
12178                         same_currency := false;
12179                         currency_ratio := new_amount / old_amount;
12180                 END IF;
12181         END IF;
12182         --
12183         -- Identify the funding source(s) from which we want to transfer the money.
12184         -- The principle is that we want to transfer the newest money first, because
12185         -- we spend the oldest money first.  The priority for spending is defined
12186         -- by a sort of the view acq.ordered_funding_source_credit.
12187         --
12188         FOR source in
12189                 SELECT
12190                         ofsc.id,
12191                         ofsc.funding_source,
12192                         ofsc.amount,
12193                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12194                                 AS converted_amt,
12195                         fs.currency_type
12196                 FROM
12197                         acq.ordered_funding_source_credit AS ofsc,
12198                         acq.funding_source fs
12199                 WHERE
12200                         ofsc.funding_source = fs.id
12201                         and ofsc.funding_source IN
12202                         (
12203                                 SELECT funding_source
12204                                 FROM acq.fund_allocation
12205                                 WHERE fund = old_fund
12206                         )
12207                         -- and
12208                         -- (
12209                         --      ofsc.funding_source = funding_source_in
12210                         --      OR funding_source_in IS NULL
12211                         -- )
12212                 ORDER BY
12213                         ofsc.sort_priority desc,
12214                         ofsc.sort_date desc,
12215                         ofsc.id desc
12216         LOOP
12217                 --
12218                 -- Determine how much money the old fund got from this funding source,
12219                 -- denominated in the currency types of the source and of the fund.
12220                 -- This result may reflect transfers from previous iterations.
12221                 --
12222                 SELECT
12223                         COALESCE( sum( amount ), 0 ),
12224                         COALESCE( sum( amount )
12225                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12226                 INTO
12227                         orig_allocated_amt,     -- in currency of the source
12228                         allocated_amt           -- in currency of the old fund
12229                 FROM
12230                         acq.fund_allocation
12231                 WHERE
12232                         fund = old_fund
12233                         and funding_source = source.funding_source;
12234                 --      
12235                 -- Determine how much to transfer from this credit, in the currency
12236                 -- of the fund.   Begin with the amount remaining to be attributed:
12237                 --
12238                 curr_old_amt := old_remaining;
12239                 --
12240                 -- Can't attribute more than was allocated from the fund:
12241                 --
12242                 IF curr_old_amt > allocated_amt THEN
12243                         curr_old_amt := allocated_amt;
12244                 END IF;
12245                 --
12246                 -- Can't attribute more than the amount of the current credit:
12247                 --
12248                 IF curr_old_amt > source.converted_amt THEN
12249                         curr_old_amt := source.converted_amt;
12250                 END IF;
12251                 --
12252                 curr_old_amt := trunc( curr_old_amt, 2 );
12253                 --
12254                 old_remaining := old_remaining - curr_old_amt;
12255                 --
12256                 -- Determine the amount to be deducted, if any,
12257                 -- from the old allocation.
12258                 --
12259                 IF old_remaining > 0 THEN
12260                         --
12261                         -- In this case we're using the whole allocation, so use that
12262                         -- amount directly instead of applying a currency translation
12263                         -- and thereby inviting round-off errors.
12264                         --
12265                         source_deduction := - orig_allocated_amt;
12266                 ELSE 
12267                         source_deduction := trunc(
12268                                 ( - curr_old_amt ) *
12269                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12270                                 2 );
12271                 END IF;
12272                 --
12273                 IF source_deduction <> 0 THEN
12274                         --
12275                         -- Insert negative allocation for old fund in fund_allocation,
12276                         -- converted into the currency of the funding source
12277                         --
12278                         INSERT INTO acq.fund_allocation (
12279                                 funding_source,
12280                                 fund,
12281                                 amount,
12282                                 allocator,
12283                                 note
12284                         ) VALUES (
12285                                 source.funding_source,
12286                                 old_fund,
12287                                 source_deduction,
12288                                 user_id,
12289                                 'Transfer to fund ' || new_fund
12290                         );
12291                 END IF;
12292                 --
12293                 IF new_fund IS NOT NULL THEN
12294                         --
12295                         -- Determine how much to add to the new fund, in
12296                         -- its currency, and how much remains to be added:
12297                         --
12298                         IF same_currency THEN
12299                                 curr_new_amt := curr_old_amt;
12300                         ELSE
12301                                 IF old_remaining = 0 THEN
12302                                         --
12303                                         -- This is the last iteration, so nothing should be left
12304                                         --
12305                                         curr_new_amt := new_remaining;
12306                                         new_remaining := 0;
12307                                 ELSE
12308                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12309                                         new_remaining := new_remaining - curr_new_amt;
12310                                 END IF;
12311                         END IF;
12312                         --
12313                         -- Determine how much to add, if any,
12314                         -- to the new fund's allocation.
12315                         --
12316                         IF old_remaining > 0 THEN
12317                                 --
12318                                 -- In this case we're using the whole allocation, so use that amount
12319                                 -- amount directly instead of applying a currency translation and
12320                                 -- thereby inviting round-off errors.
12321                                 --
12322                                 source_addition := orig_allocated_amt;
12323                         ELSIF source.currency_type = old_fund_currency THEN
12324                                 --
12325                                 -- In this case we don't need a round trip currency translation,
12326                                 -- thereby inviting round-off errors:
12327                                 --
12328                                 source_addition := curr_old_amt;
12329                         ELSE 
12330                                 source_addition := trunc(
12331                                         curr_new_amt *
12332                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12333                                         2 );
12334                         END IF;
12335                         --
12336                         IF source_addition <> 0 THEN
12337                                 --
12338                                 -- Insert positive allocation for new fund in fund_allocation,
12339                                 -- converted to the currency of the founding source
12340                                 --
12341                                 INSERT INTO acq.fund_allocation (
12342                                         funding_source,
12343                                         fund,
12344                                         amount,
12345                                         allocator,
12346                                         note
12347                                 ) VALUES (
12348                                         source.funding_source,
12349                                         new_fund,
12350                                         source_addition,
12351                                         user_id,
12352                                         'Transfer from fund ' || old_fund
12353                                 );
12354                         END IF;
12355                 END IF;
12356                 --
12357                 IF trunc( curr_old_amt, 2 ) <> 0
12358                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12359                         --
12360                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12361                         --
12362                         INSERT INTO acq.fund_transfer (
12363                                 src_fund,
12364                                 src_amount,
12365                                 dest_fund,
12366                                 dest_amount,
12367                                 transfer_user,
12368                                 note,
12369                                 funding_source_credit
12370                         ) VALUES (
12371                                 old_fund,
12372                                 trunc( curr_old_amt, 2 ),
12373                                 new_fund,
12374                                 trunc( curr_new_amt, 2 ),
12375                                 user_id,
12376                                 xfer_note,
12377                                 source.id
12378                         );
12379                 END IF;
12380                 --
12381                 if old_remaining <= 0 THEN
12382                         EXIT;                   -- Nothing more to be transferred
12383                 END IF;
12384         END LOOP;
12385 END;
12386 $$ LANGUAGE plpgsql;
12387
12388 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
12389         old_year INTEGER,
12390         user_id INTEGER,
12391         org_unit_id INTEGER
12392 ) RETURNS VOID AS $$
12393 DECLARE
12394 --
12395 new_id      INT;
12396 old_fund    RECORD;
12397 org_found   BOOLEAN;
12398 --
12399 BEGIN
12400         --
12401         -- Sanity checks
12402         --
12403         IF old_year IS NULL THEN
12404                 RAISE EXCEPTION 'Input year argument is NULL';
12405         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12406                 RAISE EXCEPTION 'Input year is out of range';
12407         END IF;
12408         --
12409         IF user_id IS NULL THEN
12410                 RAISE EXCEPTION 'Input user id argument is NULL';
12411         END IF;
12412         --
12413         IF org_unit_id IS NULL THEN
12414                 RAISE EXCEPTION 'Org unit id argument is NULL';
12415         ELSE
12416                 SELECT TRUE INTO org_found
12417                 FROM actor.org_unit
12418                 WHERE id = org_unit_id;
12419                 --
12420                 IF org_found IS NULL THEN
12421                         RAISE EXCEPTION 'Org unit id is invalid';
12422                 END IF;
12423         END IF;
12424         --
12425         -- Loop over the applicable funds
12426         --
12427         FOR old_fund in SELECT * FROM acq.fund
12428         WHERE
12429                 year = old_year
12430                 AND propagate
12431                 AND org = org_unit_id
12432         LOOP
12433                 BEGIN
12434                         INSERT INTO acq.fund (
12435                                 org,
12436                                 name,
12437                                 year,
12438                                 currency_type,
12439                                 code,
12440                                 rollover,
12441                                 propagate,
12442                                 balance_warning_percent,
12443                                 balance_stop_percent
12444                         ) VALUES (
12445                                 old_fund.org,
12446                                 old_fund.name,
12447                                 old_year + 1,
12448                                 old_fund.currency_type,
12449                                 old_fund.code,
12450                                 old_fund.rollover,
12451                                 true,
12452                                 old_fund.balance_warning_percent,
12453                                 old_fund.balance_stop_percent
12454                         )
12455                         RETURNING id INTO new_id;
12456                 EXCEPTION
12457                         WHEN unique_violation THEN
12458                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12459                                 CONTINUE;
12460                 END;
12461                 --RAISE NOTICE 'Propagating fund % to fund %',
12462                 --      old_fund.code, new_id;
12463         END LOOP;
12464 END;
12465 $$ LANGUAGE plpgsql;
12466
12467 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12468         old_year INTEGER,
12469         user_id INTEGER,
12470         org_unit_id INTEGER
12471 ) RETURNS VOID AS $$
12472 DECLARE
12473 --
12474 new_id      INT;
12475 old_fund    RECORD;
12476 org_found   BOOLEAN;
12477 --
12478 BEGIN
12479         --
12480         -- Sanity checks
12481         --
12482         IF old_year IS NULL THEN
12483                 RAISE EXCEPTION 'Input year argument is NULL';
12484         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12485                 RAISE EXCEPTION 'Input year is out of range';
12486         END IF;
12487         --
12488         IF user_id IS NULL THEN
12489                 RAISE EXCEPTION 'Input user id argument is NULL';
12490         END IF;
12491         --
12492         IF org_unit_id IS NULL THEN
12493                 RAISE EXCEPTION 'Org unit id argument is NULL';
12494         ELSE
12495                 SELECT TRUE INTO org_found
12496                 FROM actor.org_unit
12497                 WHERE id = org_unit_id;
12498                 --
12499                 IF org_found IS NULL THEN
12500                         RAISE EXCEPTION 'Org unit id is invalid';
12501                 END IF;
12502         END IF;
12503         --
12504         -- Loop over the applicable funds
12505         --
12506         FOR old_fund in SELECT * FROM acq.fund
12507         WHERE
12508                 year = old_year
12509                 AND propagate
12510                 AND org in (
12511                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12512                 )
12513         LOOP
12514                 BEGIN
12515                         INSERT INTO acq.fund (
12516                                 org,
12517                                 name,
12518                                 year,
12519                                 currency_type,
12520                                 code,
12521                                 rollover,
12522                                 propagate,
12523                                 balance_warning_percent,
12524                                 balance_stop_percent
12525                         ) VALUES (
12526                                 old_fund.org,
12527                                 old_fund.name,
12528                                 old_year + 1,
12529                                 old_fund.currency_type,
12530                                 old_fund.code,
12531                                 old_fund.rollover,
12532                                 true,
12533                                 old_fund.balance_warning_percent,
12534                                 old_fund.balance_stop_percent
12535                         )
12536                         RETURNING id INTO new_id;
12537                 EXCEPTION
12538                         WHEN unique_violation THEN
12539                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12540                                 CONTINUE;
12541                 END;
12542                 --RAISE NOTICE 'Propagating fund % to fund %',
12543                 --      old_fund.code, new_id;
12544         END LOOP;
12545 END;
12546 $$ LANGUAGE plpgsql;
12547
12548 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12549         old_year INTEGER,
12550         user_id INTEGER,
12551         org_unit_id INTEGER
12552 ) RETURNS VOID AS $$
12553 DECLARE
12554 --
12555 new_fund    INT;
12556 new_year    INT := old_year + 1;
12557 org_found   BOOL;
12558 xfer_amount NUMERIC;
12559 roll_fund   RECORD;
12560 deb         RECORD;
12561 detail      RECORD;
12562 --
12563 BEGIN
12564         --
12565         -- Sanity checks
12566         --
12567         IF old_year IS NULL THEN
12568                 RAISE EXCEPTION 'Input year argument is NULL';
12569     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12570         RAISE EXCEPTION 'Input year is out of range';
12571         END IF;
12572         --
12573         IF user_id IS NULL THEN
12574                 RAISE EXCEPTION 'Input user id argument is NULL';
12575         END IF;
12576         --
12577         IF org_unit_id IS NULL THEN
12578                 RAISE EXCEPTION 'Org unit id argument is NULL';
12579         ELSE
12580                 --
12581                 -- Validate the org unit
12582                 --
12583                 SELECT TRUE
12584                 INTO org_found
12585                 FROM actor.org_unit
12586                 WHERE id = org_unit_id;
12587                 --
12588                 IF org_found IS NULL THEN
12589                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12590                 END IF;
12591         END IF;
12592         --
12593         -- Loop over the propagable funds to identify the details
12594         -- from the old fund plus the id of the new one, if it exists.
12595         --
12596         FOR roll_fund in
12597         SELECT
12598             oldf.id AS old_fund,
12599             oldf.org,
12600             oldf.name,
12601             oldf.currency_type,
12602             oldf.code,
12603                 oldf.rollover,
12604             newf.id AS new_fund_id
12605         FROM
12606         acq.fund AS oldf
12607         LEFT JOIN acq.fund AS newf
12608                 ON ( oldf.code = newf.code )
12609         WHERE
12610                     oldf.org = org_unit_id
12611                 and oldf.year = old_year
12612                 and oldf.propagate
12613         and newf.year = new_year
12614         LOOP
12615                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12616                 --
12617                 IF roll_fund.new_fund_id IS NULL THEN
12618                         --
12619                         -- The old fund hasn't been propagated yet.  Propagate it now.
12620                         --
12621                         INSERT INTO acq.fund (
12622                                 org,
12623                                 name,
12624                                 year,
12625                                 currency_type,
12626                                 code,
12627                                 rollover,
12628                                 propagate,
12629                                 balance_warning_percent,
12630                                 balance_stop_percent
12631                         ) VALUES (
12632                                 roll_fund.org,
12633                                 roll_fund.name,
12634                                 new_year,
12635                                 roll_fund.currency_type,
12636                                 roll_fund.code,
12637                                 true,
12638                                 true,
12639                                 roll_fund.balance_warning_percent,
12640                                 roll_fund.balance_stop_percent
12641                         )
12642                         RETURNING id INTO new_fund;
12643                 ELSE
12644                         new_fund = roll_fund.new_fund_id;
12645                 END IF;
12646                 --
12647                 -- Determine the amount to transfer
12648                 --
12649                 SELECT amount
12650                 INTO xfer_amount
12651                 FROM acq.fund_spent_balance
12652                 WHERE fund = roll_fund.old_fund;
12653                 --
12654                 IF xfer_amount <> 0 THEN
12655                         IF roll_fund.rollover THEN
12656                                 --
12657                                 -- Transfer balance from old fund to new
12658                                 --
12659                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12660                                 --
12661                                 PERFORM acq.transfer_fund(
12662                                         roll_fund.old_fund,
12663                                         xfer_amount,
12664                                         new_fund,
12665                                         xfer_amount,
12666                                         user_id,
12667                                         'Rollover'
12668                                 );
12669                         ELSE
12670                                 --
12671                                 -- Transfer balance from old fund to the void
12672                                 --
12673                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12674                                 --
12675                                 PERFORM acq.transfer_fund(
12676                                         roll_fund.old_fund,
12677                                         xfer_amount,
12678                                         NULL,
12679                                         NULL,
12680                                         user_id,
12681                                         'Rollover'
12682                                 );
12683                         END IF;
12684                 END IF;
12685                 --
12686                 IF roll_fund.rollover THEN
12687                         --
12688                         -- Move any lineitems from the old fund to the new one
12689                         -- where the associated debit is an encumbrance.
12690                         --
12691                         -- Any other tables tying expenditure details to funds should
12692                         -- receive similar treatment.  At this writing there are none.
12693                         --
12694                         UPDATE acq.lineitem_detail
12695                         SET fund = new_fund
12696                         WHERE
12697                         fund = roll_fund.old_fund -- this condition may be redundant
12698                         AND fund_debit in
12699                         (
12700                                 SELECT id
12701                                 FROM acq.fund_debit
12702                                 WHERE
12703                                 fund = roll_fund.old_fund
12704                                 AND encumbrance
12705                         );
12706                         --
12707                         -- Move encumbrance debits from the old fund to the new fund
12708                         --
12709                         UPDATE acq.fund_debit
12710                         SET fund = new_fund
12711                         wHERE
12712                                 fund = roll_fund.old_fund
12713                                 AND encumbrance;
12714                 END IF;
12715                 --
12716                 -- Mark old fund as inactive, now that we've closed it
12717                 --
12718                 UPDATE acq.fund
12719                 SET active = FALSE
12720                 WHERE id = roll_fund.old_fund;
12721         END LOOP;
12722 END;
12723 $$ LANGUAGE plpgsql;
12724
12725 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12726         old_year INTEGER,
12727         user_id INTEGER,
12728         org_unit_id INTEGER
12729 ) RETURNS VOID AS $$
12730 DECLARE
12731 --
12732 new_fund    INT;
12733 new_year    INT := old_year + 1;
12734 org_found   BOOL;
12735 xfer_amount NUMERIC;
12736 roll_fund   RECORD;
12737 deb         RECORD;
12738 detail      RECORD;
12739 --
12740 BEGIN
12741         --
12742         -- Sanity checks
12743         --
12744         IF old_year IS NULL THEN
12745                 RAISE EXCEPTION 'Input year argument is NULL';
12746     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12747         RAISE EXCEPTION 'Input year is out of range';
12748         END IF;
12749         --
12750         IF user_id IS NULL THEN
12751                 RAISE EXCEPTION 'Input user id argument is NULL';
12752         END IF;
12753         --
12754         IF org_unit_id IS NULL THEN
12755                 RAISE EXCEPTION 'Org unit id argument is NULL';
12756         ELSE
12757                 --
12758                 -- Validate the org unit
12759                 --
12760                 SELECT TRUE
12761                 INTO org_found
12762                 FROM actor.org_unit
12763                 WHERE id = org_unit_id;
12764                 --
12765                 IF org_found IS NULL THEN
12766                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12767                 END IF;
12768         END IF;
12769         --
12770         -- Loop over the propagable funds to identify the details
12771         -- from the old fund plus the id of the new one, if it exists.
12772         --
12773         FOR roll_fund in
12774         SELECT
12775             oldf.id AS old_fund,
12776             oldf.org,
12777             oldf.name,
12778             oldf.currency_type,
12779             oldf.code,
12780                 oldf.rollover,
12781             newf.id AS new_fund_id
12782         FROM
12783         acq.fund AS oldf
12784         LEFT JOIN acq.fund AS newf
12785                 ON ( oldf.code = newf.code )
12786         WHERE
12787                     oldf.year = old_year
12788                 AND oldf.propagate
12789         AND newf.year = new_year
12790                 AND oldf.org in (
12791                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12792                 )
12793         LOOP
12794                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12795                 --
12796                 IF roll_fund.new_fund_id IS NULL THEN
12797                         --
12798                         -- The old fund hasn't been propagated yet.  Propagate it now.
12799                         --
12800                         INSERT INTO acq.fund (
12801                                 org,
12802                                 name,
12803                                 year,
12804                                 currency_type,
12805                                 code,
12806                                 rollover,
12807                                 propagate,
12808                                 balance_warning_percent,
12809                                 balance_stop_percent
12810                         ) VALUES (
12811                                 roll_fund.org,
12812                                 roll_fund.name,
12813                                 new_year,
12814                                 roll_fund.currency_type,
12815                                 roll_fund.code,
12816                                 true,
12817                                 true,
12818                                 roll_fund.balance_warning_percent,
12819                                 roll_fund.balance_stop_percent
12820                         )
12821                         RETURNING id INTO new_fund;
12822                 ELSE
12823                         new_fund = roll_fund.new_fund_id;
12824                 END IF;
12825                 --
12826                 -- Determine the amount to transfer
12827                 --
12828                 SELECT amount
12829                 INTO xfer_amount
12830                 FROM acq.fund_spent_balance
12831                 WHERE fund = roll_fund.old_fund;
12832                 --
12833                 IF xfer_amount <> 0 THEN
12834                         IF roll_fund.rollover THEN
12835                                 --
12836                                 -- Transfer balance from old fund to new
12837                                 --
12838                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12839                                 --
12840                                 PERFORM acq.transfer_fund(
12841                                         roll_fund.old_fund,
12842                                         xfer_amount,
12843                                         new_fund,
12844                                         xfer_amount,
12845                                         user_id,
12846                                         'Rollover'
12847                                 );
12848                         ELSE
12849                                 --
12850                                 -- Transfer balance from old fund to the void
12851                                 --
12852                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12853                                 --
12854                                 PERFORM acq.transfer_fund(
12855                                         roll_fund.old_fund,
12856                                         xfer_amount,
12857                                         NULL,
12858                                         NULL,
12859                                         user_id,
12860                                         'Rollover'
12861                                 );
12862                         END IF;
12863                 END IF;
12864                 --
12865                 IF roll_fund.rollover THEN
12866                         --
12867                         -- Move any lineitems from the old fund to the new one
12868                         -- where the associated debit is an encumbrance.
12869                         --
12870                         -- Any other tables tying expenditure details to funds should
12871                         -- receive similar treatment.  At this writing there are none.
12872                         --
12873                         UPDATE acq.lineitem_detail
12874                         SET fund = new_fund
12875                         WHERE
12876                         fund = roll_fund.old_fund -- this condition may be redundant
12877                         AND fund_debit in
12878                         (
12879                                 SELECT id
12880                                 FROM acq.fund_debit
12881                                 WHERE
12882                                 fund = roll_fund.old_fund
12883                                 AND encumbrance
12884                         );
12885                         --
12886                         -- Move encumbrance debits from the old fund to the new fund
12887                         --
12888                         UPDATE acq.fund_debit
12889                         SET fund = new_fund
12890                         wHERE
12891                                 fund = roll_fund.old_fund
12892                                 AND encumbrance;
12893                 END IF;
12894                 --
12895                 -- Mark old fund as inactive, now that we've closed it
12896                 --
12897                 UPDATE acq.fund
12898                 SET active = FALSE
12899                 WHERE id = roll_fund.old_fund;
12900         END LOOP;
12901 END;
12902 $$ LANGUAGE plpgsql;
12903
12904 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
12905     SELECT regexp_replace($1, ',', '', 'g');
12906 $$ LANGUAGE SQL STRICT IMMUTABLE;
12907
12908 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
12909     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
12910 $$ LANGUAGE SQL STRICT IMMUTABLE;
12911
12912 CREATE TABLE acq.distribution_formula_application (
12913     id BIGSERIAL PRIMARY KEY,
12914     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
12915     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
12916     formula INT NOT NULL
12917         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
12918     lineitem INT NOT NULL
12919         REFERENCES acq.lineitem( id )
12920                 ON DELETE CASCADE
12921                 DEFERRABLE INITIALLY DEFERRED
12922 );
12923
12924 CREATE INDEX acqdfa_df_idx
12925     ON acq.distribution_formula_application(formula);
12926 CREATE INDEX acqdfa_li_idx
12927     ON acq.distribution_formula_application(lineitem);
12928 CREATE INDEX acqdfa_creator_idx
12929     ON acq.distribution_formula_application(creator);
12930
12931 CREATE TABLE acq.user_request_type (
12932     id      SERIAL  PRIMARY KEY,
12933     label   TEXT    NOT NULL UNIQUE -- i18n-ize
12934 );
12935
12936 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
12937 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
12938 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
12939 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
12940 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
12941
12942 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
12943
12944 CREATE TABLE acq.cancel_reason (
12945         id            SERIAL            PRIMARY KEY,
12946         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
12947                                         DEFERRABLE INITIALLY DEFERRED,
12948         label         TEXT              NOT NULL,
12949         description   TEXT              NOT NULL,
12950         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
12951         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
12952 );
12953
12954 -- Reserve ids 1-999 for stock reasons
12955 -- Reserve ids 1000-1999 for EDI reasons
12956 -- 2000+ are available for staff to create
12957
12958 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
12959
12960 CREATE TABLE acq.user_request (
12961     id                  SERIAL  PRIMARY KEY,
12962     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
12963     hold                BOOL    NOT NULL DEFAULT TRUE,
12964
12965     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
12966     holdable_formats    TEXT,           -- nullable, for use in hold creation
12967     phone_notify        TEXT,
12968     email_notify        BOOL    NOT NULL DEFAULT TRUE,
12969     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
12970     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
12971     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
12972     need_before         TIMESTAMPTZ,    -- don't create holds after this
12973     max_fee             TEXT,
12974
12975     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
12976     isxn                TEXT,
12977     title               TEXT,
12978     volume              TEXT,
12979     author              TEXT,
12980     article_title       TEXT,
12981     article_pages       TEXT,
12982     publisher           TEXT,
12983     location            TEXT,
12984     pubdate             TEXT,
12985     mentioned           TEXT,
12986     other_info          TEXT,
12987         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
12988                                              DEFERRABLE INITIALLY DEFERRED
12989 );
12990
12991 CREATE TABLE acq.lineitem_alert_text (
12992         id               SERIAL         PRIMARY KEY,
12993         code             TEXT           NOT NULL,
12994         description      TEXT,
12995         owning_lib       INT            NOT NULL
12996                                         REFERENCES actor.org_unit(id)
12997                                         DEFERRABLE INITIALLY DEFERRED,
12998         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
12999 );
13000
13001 ALTER TABLE acq.lineitem_note
13002         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
13003                                          DEFERRABLE INITIALLY DEFERRED;
13004
13005 -- add ON DELETE CASCADE clause
13006
13007 ALTER TABLE acq.lineitem_note
13008         DROP CONSTRAINT lineitem_note_lineitem_fkey;
13009
13010 ALTER TABLE acq.lineitem_note
13011         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13012                 ON DELETE CASCADE
13013                 DEFERRABLE INITIALLY DEFERRED;
13014
13015 ALTER TABLE acq.lineitem_note
13016         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13017
13018 CREATE TABLE acq.invoice_method (
13019     code    TEXT    PRIMARY KEY,
13020     name    TEXT    NOT NULL -- i18n-ize
13021 );
13022 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
13023 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
13024
13025 CREATE TABLE acq.invoice_payment_method (
13026         code      TEXT     PRIMARY KEY,
13027         name      TEXT     NOT NULL
13028 );
13029
13030 CREATE TABLE acq.invoice (
13031     id             SERIAL      PRIMARY KEY,
13032     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
13033     provider       INT         NOT NULL REFERENCES acq.provider (id),
13034     shipper        INT         NOT NULL REFERENCES acq.provider (id),
13035     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13036     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
13037     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
13038     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
13039         payment_auth   TEXT,
13040         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
13041                                    DEFERRABLE INITIALLY DEFERRED,
13042         note           TEXT,
13043     complete       BOOL        NOT NULL DEFAULT FALSE,
13044     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13045 );
13046
13047 CREATE TABLE acq.invoice_entry (
13048     id              SERIAL      PRIMARY KEY,
13049     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13050     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13051     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13052     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13053     phys_item_count INT, -- and how many did staff count
13054     note            TEXT,
13055     billed_per_item BOOL,
13056     cost_billed     NUMERIC(8,2),
13057     actual_cost     NUMERIC(8,2),
13058         amount_paid     NUMERIC (8,2)
13059 );
13060
13061 CREATE TABLE acq.invoice_item_type (
13062     code    TEXT    PRIMARY KEY,
13063     name    TEXT    NOT NULL, -- i18n-ize
13064         prorate BOOL    NOT NULL DEFAULT FALSE
13065 );
13066
13067 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13068 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13069 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13070 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13071 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13072 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13073
13074 CREATE TABLE acq.po_item (
13075         id              SERIAL      PRIMARY KEY,
13076         purchase_order  INT         REFERENCES acq.purchase_order (id)
13077                                     ON UPDATE CASCADE ON DELETE SET NULL
13078                                     DEFERRABLE INITIALLY DEFERRED,
13079         fund_debit      INT         REFERENCES acq.fund_debit (id)
13080                                     DEFERRABLE INITIALLY DEFERRED,
13081         inv_item_type   TEXT        NOT NULL
13082                                     REFERENCES acq.invoice_item_type (code)
13083                                     DEFERRABLE INITIALLY DEFERRED,
13084         title           TEXT,
13085         author          TEXT,
13086         note            TEXT,
13087         estimated_cost  NUMERIC(8,2),
13088         fund            INT         REFERENCES acq.fund (id)
13089                                     DEFERRABLE INITIALLY DEFERRED,
13090         target          BIGINT
13091 );
13092
13093 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13094     id              SERIAL      PRIMARY KEY,
13095     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13096     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13097     fund_debit      INT         REFERENCES acq.fund_debit (id),
13098     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13099     title           TEXT,
13100     author          TEXT,
13101     note            TEXT,
13102     cost_billed     NUMERIC(8,2),
13103     actual_cost     NUMERIC(8,2),
13104     fund            INT         REFERENCES acq.fund (id)
13105                                 DEFERRABLE INITIALLY DEFERRED,
13106     amount_paid     NUMERIC (8,2),
13107     po_item         INT         REFERENCES acq.po_item (id)
13108                                 DEFERRABLE INITIALLY DEFERRED,
13109     target          BIGINT
13110 );
13111
13112 CREATE TABLE acq.edi_message (
13113     id               SERIAL          PRIMARY KEY,
13114     account          INTEGER         REFERENCES acq.edi_account(id)
13115                                      DEFERRABLE INITIALLY DEFERRED,
13116     remote_file      TEXT,
13117     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13118     translate_time   TIMESTAMPTZ,
13119     process_time     TIMESTAMPTZ,
13120     error_time       TIMESTAMPTZ,
13121     status           TEXT            NOT NULL DEFAULT 'new'
13122                                      CONSTRAINT status_value CHECK
13123                                      ( status IN (
13124                                         'new',          -- needs to be translated
13125                                         'translated',   -- needs to be processed
13126                                         'trans_error',  -- error in translation step
13127                                         'processed',    -- needs to have remote_file deleted
13128                                         'proc_error',   -- error in processing step
13129                                         'delete_error', -- error in deletion
13130                                         'retry',        -- need to retry
13131                                         'complete'      -- done
13132                                      )),
13133     edi              TEXT,
13134     jedi             TEXT,
13135     error            TEXT,
13136     purchase_order   INT             REFERENCES acq.purchase_order
13137                                      DEFERRABLE INITIALLY DEFERRED,
13138     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13139                                      CHECK ( message_type IN (
13140                                         'ORDERS',
13141                                         'ORDRSP',
13142                                         'INVOIC',
13143                                         'OSTENQ',
13144                                         'OSTRPT'
13145                                      ))
13146 );
13147
13148 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13149
13150 ALTER TABLE acq.provider_address
13151         ADD COLUMN fax_phone TEXT;
13152
13153 ALTER TABLE acq.provider_contact_address
13154         ADD COLUMN fax_phone TEXT;
13155
13156 CREATE TABLE acq.provider_note (
13157     id      SERIAL              PRIMARY KEY,
13158     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13159     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13160     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13161     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13162     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13163     value       TEXT            NOT NULL
13164 );
13165 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13166 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13167 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13168
13169 -- For each fund: the total allocation from all sources, in the
13170 -- currency of the fund (or 0 if there are no allocations)
13171
13172 CREATE VIEW acq.all_fund_allocation_total AS
13173 SELECT
13174     f.id AS fund,
13175     COALESCE( SUM( a.amount * acq.exchange_ratio(
13176         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13177     AS amount
13178 FROM
13179     acq.fund f
13180         LEFT JOIN acq.fund_allocation a
13181             ON a.fund = f.id
13182         LEFT JOIN acq.funding_source s
13183             ON a.funding_source = s.id
13184 GROUP BY
13185     f.id;
13186
13187 -- For every fund: the total encumbrances (or 0 if none),
13188 -- in the currency of the fund.
13189
13190 CREATE VIEW acq.all_fund_encumbrance_total AS
13191 SELECT
13192         f.id AS fund,
13193         COALESCE( encumb.amount, 0 ) AS amount
13194 FROM
13195         acq.fund AS f
13196                 LEFT JOIN (
13197                         SELECT
13198                                 fund,
13199                                 sum( amount ) AS amount
13200                         FROM
13201                                 acq.fund_debit
13202                         WHERE
13203                                 encumbrance
13204                         GROUP BY fund
13205                 ) AS encumb
13206                         ON f.id = encumb.fund;
13207
13208 -- For every fund: the total spent (or 0 if none),
13209 -- in the currency of the fund.
13210
13211 CREATE VIEW acq.all_fund_spent_total AS
13212 SELECT
13213     f.id AS fund,
13214     COALESCE( spent.amount, 0 ) AS amount
13215 FROM
13216     acq.fund AS f
13217         LEFT JOIN (
13218             SELECT
13219                 fund,
13220                 sum( amount ) AS amount
13221             FROM
13222                 acq.fund_debit
13223             WHERE
13224                 NOT encumbrance
13225             GROUP BY fund
13226         ) AS spent
13227             ON f.id = spent.fund;
13228
13229 -- For each fund: the amount not yet spent, in the currency
13230 -- of the fund.  May include encumbrances.
13231
13232 CREATE VIEW acq.all_fund_spent_balance AS
13233 SELECT
13234         c.fund,
13235         c.amount - d.amount AS amount
13236 FROM acq.all_fund_allocation_total c
13237     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13238
13239 -- For each fund: the amount neither spent nor encumbered,
13240 -- in the currency of the fund
13241
13242 CREATE VIEW acq.all_fund_combined_balance AS
13243 SELECT
13244      a.fund,
13245      a.amount - COALESCE( c.amount, 0 ) AS amount
13246 FROM
13247      acq.all_fund_allocation_total a
13248         LEFT OUTER JOIN (
13249             SELECT
13250                 fund,
13251                 SUM( amount ) AS amount
13252             FROM
13253                 acq.fund_debit
13254             GROUP BY
13255                 fund
13256         ) AS c USING ( fund );
13257
13258 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 $$
13259 DECLARE
13260         suffix TEXT;
13261         bucket_row RECORD;
13262         picklist_row RECORD;
13263         queue_row RECORD;
13264         folder_row RECORD;
13265 BEGIN
13266
13267     -- do some initial cleanup 
13268     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13269     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13270     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13271
13272     -- actor.*
13273     IF del_cards THEN
13274         DELETE FROM actor.card where usr = src_usr;
13275     ELSE
13276         IF deactivate_cards THEN
13277             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13278         END IF;
13279         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13280     END IF;
13281
13282
13283     IF del_addrs THEN
13284         DELETE FROM actor.usr_address WHERE usr = src_usr;
13285     ELSE
13286         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13287     END IF;
13288
13289     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13290     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13291     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13292     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13293     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13294
13295     -- permission.*
13296     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13297     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13298     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13299     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13300
13301
13302     -- container.*
13303         
13304         -- For each *_bucket table: transfer every bucket belonging to src_usr
13305         -- into the custody of dest_usr.
13306         --
13307         -- In order to avoid colliding with an existing bucket owned by
13308         -- the destination user, append the source user's id (in parenthesese)
13309         -- to the name.  If you still get a collision, add successive
13310         -- spaces to the name and keep trying until you succeed.
13311         --
13312         FOR bucket_row in
13313                 SELECT id, name
13314                 FROM   container.biblio_record_entry_bucket
13315                 WHERE  owner = src_usr
13316         LOOP
13317                 suffix := ' (' || src_usr || ')';
13318                 LOOP
13319                         BEGIN
13320                                 UPDATE  container.biblio_record_entry_bucket
13321                                 SET     owner = dest_usr, name = name || suffix
13322                                 WHERE   id = bucket_row.id;
13323                         EXCEPTION WHEN unique_violation THEN
13324                                 suffix := suffix || ' ';
13325                                 CONTINUE;
13326                         END;
13327                         EXIT;
13328                 END LOOP;
13329         END LOOP;
13330
13331         FOR bucket_row in
13332                 SELECT id, name
13333                 FROM   container.call_number_bucket
13334                 WHERE  owner = src_usr
13335         LOOP
13336                 suffix := ' (' || src_usr || ')';
13337                 LOOP
13338                         BEGIN
13339                                 UPDATE  container.call_number_bucket
13340                                 SET     owner = dest_usr, name = name || suffix
13341                                 WHERE   id = bucket_row.id;
13342                         EXCEPTION WHEN unique_violation THEN
13343                                 suffix := suffix || ' ';
13344                                 CONTINUE;
13345                         END;
13346                         EXIT;
13347                 END LOOP;
13348         END LOOP;
13349
13350         FOR bucket_row in
13351                 SELECT id, name
13352                 FROM   container.copy_bucket
13353                 WHERE  owner = src_usr
13354         LOOP
13355                 suffix := ' (' || src_usr || ')';
13356                 LOOP
13357                         BEGIN
13358                                 UPDATE  container.copy_bucket
13359                                 SET     owner = dest_usr, name = name || suffix
13360                                 WHERE   id = bucket_row.id;
13361                         EXCEPTION WHEN unique_violation THEN
13362                                 suffix := suffix || ' ';
13363                                 CONTINUE;
13364                         END;
13365                         EXIT;
13366                 END LOOP;
13367         END LOOP;
13368
13369         FOR bucket_row in
13370                 SELECT id, name
13371                 FROM   container.user_bucket
13372                 WHERE  owner = src_usr
13373         LOOP
13374                 suffix := ' (' || src_usr || ')';
13375                 LOOP
13376                         BEGIN
13377                                 UPDATE  container.user_bucket
13378                                 SET     owner = dest_usr, name = name || suffix
13379                                 WHERE   id = bucket_row.id;
13380                         EXCEPTION WHEN unique_violation THEN
13381                                 suffix := suffix || ' ';
13382                                 CONTINUE;
13383                         END;
13384                         EXIT;
13385                 END LOOP;
13386         END LOOP;
13387
13388         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13389
13390     -- vandelay.*
13391         -- transfer queues the same way we transfer buckets (see above)
13392         FOR queue_row in
13393                 SELECT id, name
13394                 FROM   vandelay.queue
13395                 WHERE  owner = src_usr
13396         LOOP
13397                 suffix := ' (' || src_usr || ')';
13398                 LOOP
13399                         BEGIN
13400                                 UPDATE  vandelay.queue
13401                                 SET     owner = dest_usr, name = name || suffix
13402                                 WHERE   id = queue_row.id;
13403                         EXCEPTION WHEN unique_violation THEN
13404                                 suffix := suffix || ' ';
13405                                 CONTINUE;
13406                         END;
13407                         EXIT;
13408                 END LOOP;
13409         END LOOP;
13410
13411     -- money.*
13412     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13413     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13414     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13415     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13416     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13417
13418     -- action.*
13419     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13420     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13421     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13422
13423     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13424     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13425     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13426     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13427
13428     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13429     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13430     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13431     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13432     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13433
13434     -- acq.*
13435     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13436         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13437
13438         -- transfer picklists the same way we transfer buckets (see above)
13439         FOR picklist_row in
13440                 SELECT id, name
13441                 FROM   acq.picklist
13442                 WHERE  owner = src_usr
13443         LOOP
13444                 suffix := ' (' || src_usr || ')';
13445                 LOOP
13446                         BEGIN
13447                                 UPDATE  acq.picklist
13448                                 SET     owner = dest_usr, name = name || suffix
13449                                 WHERE   id = picklist_row.id;
13450                         EXCEPTION WHEN unique_violation THEN
13451                                 suffix := suffix || ' ';
13452                                 CONTINUE;
13453                         END;
13454                         EXIT;
13455                 END LOOP;
13456         END LOOP;
13457
13458     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13459     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13460     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13461     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13462     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13463     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13464     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13465     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13466
13467     -- asset.*
13468     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13469     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13470     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13471     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13472     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13473     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13474
13475     -- serial.*
13476     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13477     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13478
13479     -- reporter.*
13480     -- It's not uncommon to define the reporter schema in a replica 
13481     -- DB only, so don't assume these tables exist in the write DB.
13482     BEGIN
13483         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13484     EXCEPTION WHEN undefined_table THEN
13485         -- do nothing
13486     END;
13487     BEGIN
13488         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13489     EXCEPTION WHEN undefined_table THEN
13490         -- do nothing
13491     END;
13492     BEGIN
13493         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13494     EXCEPTION WHEN undefined_table THEN
13495         -- do nothing
13496     END;
13497     BEGIN
13498                 -- transfer folders the same way we transfer buckets (see above)
13499                 FOR folder_row in
13500                         SELECT id, name
13501                         FROM   reporter.template_folder
13502                         WHERE  owner = src_usr
13503                 LOOP
13504                         suffix := ' (' || src_usr || ')';
13505                         LOOP
13506                                 BEGIN
13507                                         UPDATE  reporter.template_folder
13508                                         SET     owner = dest_usr, name = name || suffix
13509                                         WHERE   id = folder_row.id;
13510                                 EXCEPTION WHEN unique_violation THEN
13511                                         suffix := suffix || ' ';
13512                                         CONTINUE;
13513                                 END;
13514                                 EXIT;
13515                         END LOOP;
13516                 END LOOP;
13517     EXCEPTION WHEN undefined_table THEN
13518         -- do nothing
13519     END;
13520     BEGIN
13521                 -- transfer folders the same way we transfer buckets (see above)
13522                 FOR folder_row in
13523                         SELECT id, name
13524                         FROM   reporter.report_folder
13525                         WHERE  owner = src_usr
13526                 LOOP
13527                         suffix := ' (' || src_usr || ')';
13528                         LOOP
13529                                 BEGIN
13530                                         UPDATE  reporter.report_folder
13531                                         SET     owner = dest_usr, name = name || suffix
13532                                         WHERE   id = folder_row.id;
13533                                 EXCEPTION WHEN unique_violation THEN
13534                                         suffix := suffix || ' ';
13535                                         CONTINUE;
13536                                 END;
13537                                 EXIT;
13538                         END LOOP;
13539                 END LOOP;
13540     EXCEPTION WHEN undefined_table THEN
13541         -- do nothing
13542     END;
13543     BEGIN
13544                 -- transfer folders the same way we transfer buckets (see above)
13545                 FOR folder_row in
13546                         SELECT id, name
13547                         FROM   reporter.output_folder
13548                         WHERE  owner = src_usr
13549                 LOOP
13550                         suffix := ' (' || src_usr || ')';
13551                         LOOP
13552                                 BEGIN
13553                                         UPDATE  reporter.output_folder
13554                                         SET     owner = dest_usr, name = name || suffix
13555                                         WHERE   id = folder_row.id;
13556                                 EXCEPTION WHEN unique_violation THEN
13557                                         suffix := suffix || ' ';
13558                                         CONTINUE;
13559                                 END;
13560                                 EXIT;
13561                         END LOOP;
13562                 END LOOP;
13563     EXCEPTION WHEN undefined_table THEN
13564         -- do nothing
13565     END;
13566
13567     -- Finally, delete the source user
13568     DELETE FROM actor.usr WHERE id = src_usr;
13569
13570 END;
13571 $$ LANGUAGE plpgsql;
13572
13573 -- The "add" trigger functions should protect against existing NULLed values, just in case
13574 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13575 BEGIN
13576     IF NOT NEW.voided THEN
13577         UPDATE  money.materialized_billable_xact_summary
13578           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13579             last_billing_ts = NEW.billing_ts,
13580             last_billing_note = NEW.note,
13581             last_billing_type = NEW.billing_type,
13582             balance_owed = balance_owed + NEW.amount
13583           WHERE id = NEW.xact;
13584     END IF;
13585
13586     RETURN NEW;
13587 END;
13588 $$ LANGUAGE PLPGSQL;
13589
13590 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13591 BEGIN
13592     IF NOT NEW.voided THEN
13593         UPDATE  money.materialized_billable_xact_summary
13594           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13595             last_payment_ts = NEW.payment_ts,
13596             last_payment_note = NEW.note,
13597             last_payment_type = TG_ARGV[0],
13598             balance_owed = balance_owed - NEW.amount
13599           WHERE id = NEW.xact;
13600     END IF;
13601
13602     RETURN NEW;
13603 END;
13604 $$ LANGUAGE PLPGSQL;
13605
13606 -- Refresh the mat view with the corrected underlying view
13607 TRUNCATE money.materialized_billable_xact_summary;
13608 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13609
13610 -- Now redefine the view as a window onto the materialized view
13611 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13612     SELECT * FROM money.materialized_billable_xact_summary;
13613
13614 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13615     user_id    IN INTEGER,
13616     perm_code  IN TEXT
13617 )
13618 RETURNS SETOF INTEGER AS $$
13619 --
13620 -- Return a set of all the org units for which a given user has a given
13621 -- permission, granted directly (not through inheritance from a parent
13622 -- org unit).
13623 --
13624 -- The permissions apply to a minimum depth of the org unit hierarchy,
13625 -- for the org unit(s) to which the user is assigned.  (They also apply
13626 -- to the subordinates of those org units, but we don't report the
13627 -- subordinates here.)
13628 --
13629 -- For purposes of this function, the permission.usr_work_ou_map table
13630 -- defines which users belong to which org units.  I.e. we ignore the
13631 -- home_ou column of actor.usr.
13632 --
13633 -- The result set may contain duplicates, which should be eliminated
13634 -- by a DISTINCT clause.
13635 --
13636 DECLARE
13637     b_super       BOOLEAN;
13638     n_perm        INTEGER;
13639     n_min_depth   INTEGER;
13640     n_work_ou     INTEGER;
13641     n_curr_ou     INTEGER;
13642     n_depth       INTEGER;
13643     n_curr_depth  INTEGER;
13644 BEGIN
13645     --
13646     -- Check for superuser
13647     --
13648     SELECT INTO b_super
13649         super_user
13650     FROM
13651         actor.usr
13652     WHERE
13653         id = user_id;
13654     --
13655     IF NOT FOUND THEN
13656         return;             -- No user?  No permissions.
13657     ELSIF b_super THEN
13658         --
13659         -- Super user has all permissions everywhere
13660         --
13661         FOR n_work_ou IN
13662             SELECT
13663                 id
13664             FROM
13665                 actor.org_unit
13666             WHERE
13667                 parent_ou IS NULL
13668         LOOP
13669             RETURN NEXT n_work_ou;
13670         END LOOP;
13671         RETURN;
13672     END IF;
13673     --
13674     -- Translate the permission name
13675     -- to a numeric permission id
13676     --
13677     SELECT INTO n_perm
13678         id
13679     FROM
13680         permission.perm_list
13681     WHERE
13682         code = perm_code;
13683     --
13684     IF NOT FOUND THEN
13685         RETURN;               -- No such permission
13686     END IF;
13687     --
13688     -- Find the highest-level org unit (i.e. the minimum depth)
13689     -- to which the permission is applied for this user
13690     --
13691     -- This query is modified from the one in permission.usr_perms().
13692     --
13693     SELECT INTO n_min_depth
13694         min( depth )
13695     FROM    (
13696         SELECT depth
13697           FROM permission.usr_perm_map upm
13698          WHERE upm.usr = user_id
13699            AND (upm.perm = n_perm OR upm.perm = -1)
13700                     UNION
13701         SELECT  gpm.depth
13702           FROM  permission.grp_perm_map gpm
13703           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13704             AND gpm.grp IN (
13705                SELECT   (permission.grp_ancestors(
13706                     (SELECT profile FROM actor.usr WHERE id = user_id)
13707                 )).id
13708             )
13709                     UNION
13710         SELECT  p.depth
13711           FROM  permission.grp_perm_map p
13712           WHERE (p.perm = n_perm OR p.perm = -1)
13713             AND p.grp IN (
13714                 SELECT (permission.grp_ancestors(m.grp)).id
13715                 FROM   permission.usr_grp_map m
13716                 WHERE  m.usr = user_id
13717             )
13718     ) AS x;
13719     --
13720     IF NOT FOUND THEN
13721         RETURN;                -- No such permission for this user
13722     END IF;
13723     --
13724     -- Identify the org units to which the user is assigned.  Note that
13725     -- we pay no attention to the home_ou column in actor.usr.
13726     --
13727     FOR n_work_ou IN
13728         SELECT
13729             work_ou
13730         FROM
13731             permission.usr_work_ou_map
13732         WHERE
13733             usr = user_id
13734     LOOP            -- For each org unit to which the user is assigned
13735         --
13736         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13737         -- We take it on faith that this depth agrees with the actual hierarchy
13738         -- defined in actor.org_unit.
13739         --
13740         SELECT INTO n_depth
13741             type.depth
13742         FROM
13743             actor.org_unit_type type
13744                 INNER JOIN actor.org_unit ou
13745                     ON ( ou.ou_type = type.id )
13746         WHERE
13747             ou.id = n_work_ou;
13748         --
13749         IF NOT FOUND THEN
13750             CONTINUE;        -- Maybe raise exception?
13751         END IF;
13752         --
13753         -- Compare the depth of the work org unit to the
13754         -- minimum depth, and branch accordingly
13755         --
13756         IF n_depth = n_min_depth THEN
13757             --
13758             -- The org unit is at the right depth, so return it.
13759             --
13760             RETURN NEXT n_work_ou;
13761         ELSIF n_depth > n_min_depth THEN
13762             --
13763             -- Traverse the org unit tree toward the root,
13764             -- until you reach the minimum depth determined above
13765             --
13766             n_curr_depth := n_depth;
13767             n_curr_ou := n_work_ou;
13768             WHILE n_curr_depth > n_min_depth LOOP
13769                 SELECT INTO n_curr_ou
13770                     parent_ou
13771                 FROM
13772                     actor.org_unit
13773                 WHERE
13774                     id = n_curr_ou;
13775                 --
13776                 IF FOUND THEN
13777                     n_curr_depth := n_curr_depth - 1;
13778                 ELSE
13779                     --
13780                     -- This can happen only if the hierarchy defined in
13781                     -- actor.org_unit is corrupted, or out of sync with
13782                     -- the depths defined in actor.org_unit_type.
13783                     -- Maybe we should raise an exception here, instead
13784                     -- of silently ignoring the problem.
13785                     --
13786                     n_curr_ou = NULL;
13787                     EXIT;
13788                 END IF;
13789             END LOOP;
13790             --
13791             IF n_curr_ou IS NOT NULL THEN
13792                 RETURN NEXT n_curr_ou;
13793             END IF;
13794         ELSE
13795             --
13796             -- The permission applies only at a depth greater than the work org unit.
13797             -- Use connectby() to find all dependent org units at the specified depth.
13798             --
13799             FOR n_curr_ou IN
13800                 SELECT ou::INTEGER
13801                 FROM connectby(
13802                         'actor.org_unit',         -- table name
13803                         'id',                     -- key column
13804                         'parent_ou',              -- recursive foreign key
13805                         n_work_ou::TEXT,          -- id of starting point
13806                         (n_min_depth - n_depth)   -- max depth to search, relative
13807                     )                             --   to starting point
13808                     AS t(
13809                         ou text,            -- dependent org unit
13810                         parent_ou text,     -- (ignore)
13811                         level int           -- depth relative to starting point
13812                     )
13813                 WHERE
13814                     level = n_min_depth - n_depth
13815             LOOP
13816                 RETURN NEXT n_curr_ou;
13817             END LOOP;
13818         END IF;
13819         --
13820     END LOOP;
13821     --
13822     RETURN;
13823     --
13824 END;
13825 $$ LANGUAGE 'plpgsql';
13826
13827 ALTER TABLE acq.purchase_order
13828         ADD COLUMN cancel_reason INT
13829                 REFERENCES acq.cancel_reason( id )
13830             DEFERRABLE INITIALLY DEFERRED,
13831         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
13832
13833 -- Build the history table and lifecycle view
13834 -- for acq.purchase_order
13835
13836 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
13837
13838 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
13839
13840 ALTER TABLE acq.lineitem
13841         ADD COLUMN cancel_reason INT
13842                 REFERENCES acq.cancel_reason( id )
13843             DEFERRABLE INITIALLY DEFERRED,
13844         ADD COLUMN estimated_unit_price NUMERIC,
13845         ADD COLUMN claim_policy INT
13846                 REFERENCES acq.claim_policy
13847                 DEFERRABLE INITIALLY DEFERRED,
13848         ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
13849
13850 -- Build the history table and lifecycle view
13851 -- for acq.lineitem
13852
13853 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
13854 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
13855
13856 ALTER TABLE acq.lineitem_detail
13857         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
13858                                             DEFERRABLE INITIALLY DEFERRED;
13859
13860 ALTER TABLE acq.lineitem_detail
13861         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
13862
13863 ALTER TABLE acq.lineitem_detail
13864         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13865                 ON DELETE CASCADE
13866                 DEFERRABLE INITIALLY DEFERRED;
13867
13868 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
13869
13870 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
13871         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
13872
13873 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
13874         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
13875
13876 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
13877
13878     use MARC::Record;
13879     use MARC::File::XML (BinaryEncoding => 'UTF-8');
13880     use strict;
13881
13882     my $target_xml = shift;
13883     my $source_xml = shift;
13884     my $field_spec = shift;
13885
13886     my $target_r = MARC::Record->new_from_xml( $target_xml );
13887     my $source_r = MARC::Record->new_from_xml( $source_xml );
13888
13889     return $target_xml unless ($target_r && $source_r);
13890
13891     my @field_list = split(',', $field_spec);
13892
13893     my %fields;
13894     for my $f (@field_list) {
13895         $f =~ s/^\s*//; $f =~ s/\s*$//;
13896         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
13897             my $field = $1;
13898             $field =~ s/\s+//;
13899             my $sf = $2;
13900             $sf =~ s/\s+//;
13901             my $match = $3;
13902             $match =~ s/^\s*//; $match =~ s/\s*$//;
13903             $fields{$field} = { sf => [ split('', $sf) ] };
13904             if ($match) {
13905                 my ($msf,$mre) = split('~', $match);
13906                 if (length($msf) > 0 and length($mre) > 0) {
13907                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
13908                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
13909                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
13910                 }
13911             }
13912         }
13913     }
13914
13915     for my $f ( keys %fields) {
13916         if ( @{$fields{$f}{sf}} ) {
13917             for my $from_field ($source_r->field( $f )) {
13918                 for my $to_field ($target_r->field( $f )) {
13919                     if (exists($fields{$f}{match})) {
13920                         next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
13921                     }
13922                     my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
13923                     $to_field->add_subfields( @new_sf );
13924                 }
13925             }
13926         } else {
13927             my @new_fields = map { $_->clone } $source_r->field( $f );
13928             $target_r->insert_fields_ordered( @new_fields );
13929         }
13930     }
13931
13932     $target_xml = $target_r->as_xml_record;
13933     $target_xml =~ s/^<\?.+?\?>$//mo;
13934     $target_xml =~ s/\n//sgo;
13935     $target_xml =~ s/>\s+</></sgo;
13936
13937     return $target_xml;
13938
13939 $_$ LANGUAGE PLPERLU;
13940
13941 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
13942
13943     use MARC::Record;
13944     use MARC::File::XML (BinaryEncoding => 'UTF-8');
13945     use strict;
13946
13947     my $xml = shift;
13948     my $r = MARC::Record->new_from_xml( $xml );
13949
13950     return $xml unless ($r);
13951
13952     my $field_spec = shift;
13953     my @field_list = split(',', $field_spec);
13954
13955     my %fields;
13956     for my $f (@field_list) {
13957         $f =~ s/^\s*//; $f =~ s/\s*$//;
13958         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
13959             my $field = $1;
13960             $field =~ s/\s+//;
13961             my $sf = $2;
13962             $sf =~ s/\s+//;
13963             my $match = $3;
13964             $match =~ s/^\s*//; $match =~ s/\s*$//;
13965             $fields{$field} = { sf => [ split('', $sf) ] };
13966             if ($match) {
13967                 my ($msf,$mre) = split('~', $match);
13968                 if (length($msf) > 0 and length($mre) > 0) {
13969                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
13970                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
13971                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
13972                 }
13973             }
13974         }
13975     }
13976
13977     for my $f ( keys %fields) {
13978         for my $to_field ($r->field( $f )) {
13979             if (exists($fields{$f}{match})) {
13980                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
13981             }
13982
13983             if ( @{$fields{$f}{sf}} ) {
13984                 $to_field->delete_subfield(code => $fields{$f}{sf});
13985             } else {
13986                 $r->delete_field( $to_field );
13987             }
13988         }
13989     }
13990
13991     $xml = $r->as_xml_record;
13992     $xml =~ s/^<\?.+?\?>$//mo;
13993     $xml =~ s/\n//sgo;
13994     $xml =~ s/>\s+</></sgo;
13995
13996     return $xml;
13997
13998 $_$ LANGUAGE PLPERLU;
13999
14000 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14001     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
14002 $_$ LANGUAGE SQL;
14003
14004 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14005     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
14006 $_$ LANGUAGE SQL;
14007
14008 CREATE VIEW action.unfulfilled_hold_max_loop AS
14009         SELECT  hold,
14010                 max(count) AS max
14011         FROM    action.unfulfilled_hold_loops
14012         GROUP BY 1;
14013
14014 ALTER TABLE acq.lineitem_attr
14015         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
14016
14017 ALTER TABLE acq.lineitem_attr
14018         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14019                 ON DELETE CASCADE
14020                 DEFERRABLE INITIALLY DEFERRED;
14021
14022 ALTER TABLE acq.po_note
14023         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
14024
14025 CREATE TABLE vandelay.merge_profile (
14026     id              BIGSERIAL   PRIMARY KEY,
14027     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
14028     name            TEXT        NOT NULL,
14029     add_spec        TEXT,
14030     replace_spec    TEXT,
14031     strip_spec      TEXT,
14032     preserve_spec   TEXT,
14033     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14034     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))
14035 );
14036
14037 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14038 DECLARE
14039     attr        RECORD;
14040     attr_def    RECORD;
14041     eg_rec      RECORD;
14042     id_value    TEXT;
14043     exact_id    BIGINT;
14044 BEGIN
14045
14046     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14047
14048     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14049
14050     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14051         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14052
14053         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14054             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14055             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14056             IF exact_id IS NOT NULL THEN
14057                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14058             END IF;
14059         END IF;
14060     END IF;
14061
14062     IF exact_id IS NULL THEN
14063         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
14064
14065             -- All numbers? check for an id match
14066             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14067                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14068                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14069                 END LOOP;
14070             END IF;
14071
14072             -- Looks like an ISBN? check for an isbn match
14073             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14074                 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
14075                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14076                     IF FOUND THEN
14077                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14078                     END IF;
14079                 END LOOP;
14080
14081                 -- subcheck for isbn-as-tcn
14082                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14083                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14084                 END LOOP;
14085             END IF;
14086
14087             -- check for an OCLC tcn_value match
14088             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14089                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14090                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14091                 END LOOP;
14092             END IF;
14093
14094             -- check for a direct tcn_value match
14095             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14096                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14097             END LOOP;
14098
14099             -- check for a direct item barcode match
14100             FOR eg_rec IN
14101                     SELECT  DISTINCT b.*
14102                       FROM  biblio.record_entry b
14103                             JOIN asset.call_number cn ON (cn.record = b.id)
14104                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14105                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14106             LOOP
14107                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14108             END LOOP;
14109
14110         END LOOP;
14111     END IF;
14112
14113     RETURN NULL;
14114 END;
14115 $func$ LANGUAGE PLPGSQL;
14116
14117 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 $_$
14118     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14119 $_$ LANGUAGE SQL;
14120
14121 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14122 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14123 DECLARE
14124     output              vandelay.compile_profile%ROWTYPE;
14125     profile             vandelay.merge_profile%ROWTYPE;
14126     profile_tmpl        TEXT;
14127     profile_tmpl_owner  TEXT;
14128     add_rule            TEXT := '';
14129     strip_rule          TEXT := '';
14130     replace_rule        TEXT := '';
14131     preserve_rule       TEXT := '';
14132
14133 BEGIN
14134
14135     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14136     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14137
14138     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14139         SELECT  p.* INTO profile
14140           FROM  vandelay.merge_profile p
14141                 JOIN actor.org_unit u ON (u.id = p.owner)
14142           WHERE p.name = profile_tmpl
14143                 AND u.shortname = profile_tmpl_owner;
14144
14145         IF profile.id IS NOT NULL THEN
14146             add_rule := COALESCE(profile.add_spec,'');
14147             strip_rule := COALESCE(profile.strip_spec,'');
14148             replace_rule := COALESCE(profile.replace_spec,'');
14149             preserve_rule := COALESCE(profile.preserve_spec,'');
14150         END IF;
14151     END IF;
14152
14153     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
14154     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
14155     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
14156     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
14157
14158     output.add_rule := BTRIM(add_rule,',');
14159     output.replace_rule := BTRIM(replace_rule,',');
14160     output.strip_rule := BTRIM(strip_rule,',');
14161     output.preserve_rule := BTRIM(preserve_rule,',');
14162
14163     RETURN output;
14164 END;
14165 $_$ LANGUAGE PLPGSQL;
14166
14167 -- Template-based marc munging functions
14168 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14169 DECLARE
14170     merge_profile   vandelay.merge_profile%ROWTYPE;
14171     dyn_profile     vandelay.compile_profile%ROWTYPE;
14172     editor_string   TEXT;
14173     editor_id       INT;
14174     source_marc     TEXT;
14175     target_marc     TEXT;
14176     eg_marc         TEXT;
14177     replace_rule    TEXT;
14178     match_count     INT;
14179 BEGIN
14180
14181     SELECT  b.marc INTO eg_marc
14182       FROM  biblio.record_entry b
14183       WHERE b.id = eg_id
14184       LIMIT 1;
14185
14186     IF eg_marc IS NULL OR v_marc IS NULL THEN
14187         -- RAISE NOTICE 'no marc for template or bib record';
14188         RETURN FALSE;
14189     END IF;
14190
14191     dyn_profile := vandelay.compile_profile( v_marc );
14192
14193     IF merge_profile_id IS NOT NULL THEN
14194         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14195         IF FOUND THEN
14196             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14197             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14198             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14199             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14200         END IF;
14201     END IF;
14202
14203     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14204         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14205         RETURN FALSE;
14206     END IF;
14207
14208     IF dyn_profile.replace_rule <> '' THEN
14209         source_marc = v_marc;
14210         target_marc = eg_marc;
14211         replace_rule = dyn_profile.replace_rule;
14212     ELSE
14213         source_marc = eg_marc;
14214         target_marc = v_marc;
14215         replace_rule = dyn_profile.preserve_rule;
14216     END IF;
14217
14218     UPDATE  biblio.record_entry
14219       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14220       WHERE id = eg_id;
14221
14222     IF NOT FOUND THEN
14223         -- RAISE NOTICE 'update of biblio.record_entry failed';
14224         RETURN FALSE;
14225     END IF;
14226
14227     RETURN TRUE;
14228
14229 END;
14230 $$ LANGUAGE PLPGSQL;
14231
14232 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14233     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14234 $$ LANGUAGE SQL;
14235
14236 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14237 DECLARE
14238     merge_profile   vandelay.merge_profile%ROWTYPE;
14239     dyn_profile     vandelay.compile_profile%ROWTYPE;
14240     editor_string   TEXT;
14241     editor_id       INT;
14242     source_marc     TEXT;
14243     target_marc     TEXT;
14244     eg_marc         TEXT;
14245     v_marc          TEXT;
14246     replace_rule    TEXT;
14247     match_count     INT;
14248 BEGIN
14249
14250     SELECT  q.marc INTO v_marc
14251       FROM  vandelay.queued_record q
14252             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14253       LIMIT 1;
14254
14255     IF v_marc IS NULL THEN
14256         -- RAISE NOTICE 'no marc for vandelay or bib record';
14257         RETURN FALSE;
14258     END IF;
14259
14260     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14261         UPDATE  vandelay.queued_bib_record
14262           SET   imported_as = eg_id,
14263                 import_time = NOW()
14264           WHERE id = import_id;
14265
14266         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14267
14268         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14269             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14270
14271             IF editor_id IS NULL THEN
14272                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14273             END IF;
14274
14275             IF editor_id IS NOT NULL THEN
14276                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14277             END IF;
14278         END IF;
14279
14280         RETURN TRUE;
14281     END IF;
14282
14283     -- RAISE NOTICE 'update of biblio.record_entry failed';
14284
14285     RETURN FALSE;
14286
14287 END;
14288 $$ LANGUAGE PLPGSQL;
14289
14290 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14291 DECLARE
14292     eg_id           BIGINT;
14293     match_count     INT;
14294     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14295 BEGIN
14296
14297     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14298
14299     IF FOUND THEN
14300         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14301         RETURN FALSE;
14302     END IF;
14303
14304     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14305
14306     IF match_count <> 1 THEN
14307         -- RAISE NOTICE 'not an exact match';
14308         RETURN FALSE;
14309     END IF;
14310
14311     SELECT  d.* INTO match_attr
14312       FROM  vandelay.bib_attr_definition d
14313             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14314             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14315       WHERE m.queued_record = import_id;
14316
14317     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14318         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14319         RETURN FALSE;
14320     END IF;
14321
14322     SELECT  m.eg_record INTO eg_id
14323       FROM  vandelay.bib_match m
14324       WHERE m.queued_record = import_id
14325       LIMIT 1;
14326
14327     IF eg_id IS NULL THEN
14328         RETURN FALSE;
14329     END IF;
14330
14331     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14332 END;
14333 $$ LANGUAGE PLPGSQL;
14334
14335 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14336 DECLARE
14337     queued_record   vandelay.queued_bib_record%ROWTYPE;
14338 BEGIN
14339
14340     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14341
14342         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14343             RETURN NEXT queued_record.id;
14344         END IF;
14345
14346     END LOOP;
14347
14348     RETURN;
14349
14350 END;
14351 $$ LANGUAGE PLPGSQL;
14352
14353 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14354     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14355 $$ LANGUAGE SQL;
14356
14357 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14358 DECLARE
14359     merge_profile   vandelay.merge_profile%ROWTYPE;
14360     dyn_profile     vandelay.compile_profile%ROWTYPE;
14361     source_marc     TEXT;
14362     target_marc     TEXT;
14363     eg_marc         TEXT;
14364     v_marc          TEXT;
14365     replace_rule    TEXT;
14366     match_count     INT;
14367 BEGIN
14368
14369     SELECT  b.marc INTO eg_marc
14370       FROM  authority.record_entry b
14371             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14372       LIMIT 1;
14373
14374     SELECT  q.marc INTO v_marc
14375       FROM  vandelay.queued_record q
14376             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14377       LIMIT 1;
14378
14379     IF eg_marc IS NULL OR v_marc IS NULL THEN
14380         -- RAISE NOTICE 'no marc for vandelay or authority record';
14381         RETURN FALSE;
14382     END IF;
14383
14384     dyn_profile := vandelay.compile_profile( v_marc );
14385
14386     IF merge_profile_id IS NOT NULL THEN
14387         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14388         IF FOUND THEN
14389             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14390             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14391             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14392             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14393         END IF;
14394     END IF;
14395
14396     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14397         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14398         RETURN FALSE;
14399     END IF;
14400
14401     IF dyn_profile.replace_rule <> '' THEN
14402         source_marc = v_marc;
14403         target_marc = eg_marc;
14404         replace_rule = dyn_profile.replace_rule;
14405     ELSE
14406         source_marc = eg_marc;
14407         target_marc = v_marc;
14408         replace_rule = dyn_profile.preserve_rule;
14409     END IF;
14410
14411     UPDATE  authority.record_entry
14412       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14413       WHERE id = eg_id;
14414
14415     IF FOUND THEN
14416         UPDATE  vandelay.queued_authority_record
14417           SET   imported_as = eg_id,
14418                 import_time = NOW()
14419           WHERE id = import_id;
14420         RETURN TRUE;
14421     END IF;
14422
14423     -- RAISE NOTICE 'update of authority.record_entry failed';
14424
14425     RETURN FALSE;
14426
14427 END;
14428 $$ LANGUAGE PLPGSQL;
14429
14430 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14431 DECLARE
14432     eg_id           BIGINT;
14433     match_count     INT;
14434 BEGIN
14435     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14436
14437     IF match_count <> 1 THEN
14438         -- RAISE NOTICE 'not an exact match';
14439         RETURN FALSE;
14440     END IF;
14441
14442     SELECT  m.eg_record INTO eg_id
14443       FROM  vandelay.authority_match m
14444       WHERE m.queued_record = import_id
14445       LIMIT 1;
14446
14447     IF eg_id IS NULL THEN
14448         RETURN FALSE;
14449     END IF;
14450
14451     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14452 END;
14453 $$ LANGUAGE PLPGSQL;
14454
14455 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14456 DECLARE
14457     queued_record   vandelay.queued_authority_record%ROWTYPE;
14458 BEGIN
14459
14460     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14461
14462         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14463             RETURN NEXT queued_record.id;
14464         END IF;
14465
14466     END LOOP;
14467
14468     RETURN;
14469
14470 END;
14471 $$ LANGUAGE PLPGSQL;
14472
14473 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14474     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14475 $$ LANGUAGE SQL;
14476
14477 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14478 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14479 DECLARE
14480     eg_tcn          TEXT;
14481     eg_tcn_source   TEXT;
14482     output          vandelay.tcn_data%ROWTYPE;
14483 BEGIN
14484
14485     -- 001/003
14486     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14487     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14488
14489         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14490         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14491             eg_tcn_source := 'System Local';
14492         END IF;
14493
14494         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14495
14496         IF NOT FOUND THEN
14497             output.used := FALSE;
14498         ELSE
14499             output.used := TRUE;
14500         END IF;
14501
14502         output.tcn := eg_tcn;
14503         output.tcn_source := eg_tcn_source;
14504         RETURN NEXT output;
14505
14506     END IF;
14507
14508     -- 901 ab
14509     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14510     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14511
14512         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14513         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14514             eg_tcn_source := 'System Local';
14515         END IF;
14516
14517         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14518
14519         IF NOT FOUND THEN
14520             output.used := FALSE;
14521         ELSE
14522             output.used := TRUE;
14523         END IF;
14524
14525         output.tcn := eg_tcn;
14526         output.tcn_source := eg_tcn_source;
14527         RETURN NEXT output;
14528
14529     END IF;
14530
14531     -- 039 ab
14532     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14533     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14534
14535         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14536         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14537             eg_tcn_source := 'System Local';
14538         END IF;
14539
14540         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14541
14542         IF NOT FOUND THEN
14543             output.used := FALSE;
14544         ELSE
14545             output.used := TRUE;
14546         END IF;
14547
14548         output.tcn := eg_tcn;
14549         output.tcn_source := eg_tcn_source;
14550         RETURN NEXT output;
14551
14552     END IF;
14553
14554     -- 020 a
14555     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14556     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14557
14558         eg_tcn_source := 'ISBN';
14559
14560         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14561
14562         IF NOT FOUND THEN
14563             output.used := FALSE;
14564         ELSE
14565             output.used := TRUE;
14566         END IF;
14567
14568         output.tcn := eg_tcn;
14569         output.tcn_source := eg_tcn_source;
14570         RETURN NEXT output;
14571
14572     END IF;
14573
14574     -- 022 a
14575     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14576     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14577
14578         eg_tcn_source := 'ISSN';
14579
14580         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14581
14582         IF NOT FOUND THEN
14583             output.used := FALSE;
14584         ELSE
14585             output.used := TRUE;
14586         END IF;
14587
14588         output.tcn := eg_tcn;
14589         output.tcn_source := eg_tcn_source;
14590         RETURN NEXT output;
14591
14592     END IF;
14593
14594     -- 010 a
14595     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14596     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14597
14598         eg_tcn_source := 'LCCN';
14599
14600         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14601
14602         IF NOT FOUND THEN
14603             output.used := FALSE;
14604         ELSE
14605             output.used := TRUE;
14606         END IF;
14607
14608         output.tcn := eg_tcn;
14609         output.tcn_source := eg_tcn_source;
14610         RETURN NEXT output;
14611
14612     END IF;
14613
14614     -- 035 a
14615     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
14616     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14617
14618         eg_tcn_source := 'System Legacy';
14619
14620         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14621
14622         IF NOT FOUND THEN
14623             output.used := FALSE;
14624         ELSE
14625             output.used := TRUE;
14626         END IF;
14627
14628         output.tcn := eg_tcn;
14629         output.tcn_source := eg_tcn_source;
14630         RETURN NEXT output;
14631
14632     END IF;
14633
14634     RETURN;
14635 END;
14636 $_$ LANGUAGE PLPGSQL;
14637
14638 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14639
14640 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);
14641
14642 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
14643
14644 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14645 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14646 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14647 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14648 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14649
14650 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14651 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14652 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14653 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14654
14655 ALTER TABLE metabib.series_field_entry
14656         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14657                 REFERENCES biblio.record_entry (id)
14658                 ON DELETE CASCADE
14659                 DEFERRABLE INITIALLY DEFERRED;
14660
14661 ALTER TABLE metabib.series_field_entry
14662         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14663                 REFERENCES config.metabib_field (id)
14664                 ON DELETE CASCADE
14665                 DEFERRABLE INITIALLY DEFERRED;
14666
14667 CREATE TABLE acq.claim_policy_action (
14668         id              SERIAL       PRIMARY KEY,
14669         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14670                                  ON DELETE CASCADE
14671                                      DEFERRABLE INITIALLY DEFERRED,
14672         action_interval INTERVAL     NOT NULL,
14673         action          INT          NOT NULL REFERENCES acq.claim_event_type
14674                                      DEFERRABLE INITIALLY DEFERRED,
14675         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14676 );
14677
14678 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14679 DECLARE
14680     value       TEXT;
14681     atype       TEXT;
14682     prov        INT;
14683     pos         INT;
14684     adef        RECORD;
14685     xpath_string    TEXT;
14686 BEGIN
14687     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14688  
14689         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14690  
14691         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14692             IF (atype = 'lineitem_provider_attr_definition') THEN
14693                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14694                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14695             END IF;
14696  
14697             IF (atype = 'lineitem_provider_attr_definition') THEN
14698                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14699             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14700                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14701             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14702                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14703             END IF;
14704  
14705             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14706  
14707             pos := 1;
14708  
14709             LOOP
14710                 SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14711  
14712                 IF (value IS NOT NULL AND value <> '') THEN
14713                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14714                         VALUES (NEW.id, adef.id, atype, adef.code, value);
14715                 ELSE
14716                     EXIT;
14717                 END IF;
14718  
14719                 pos := pos + 1;
14720             END LOOP;
14721  
14722         END IF;
14723  
14724     END LOOP;
14725  
14726     RETURN NULL;
14727 END;
14728 $function$ LANGUAGE PLPGSQL;
14729
14730 UPDATE config.metabib_field SET label = name;
14731 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14732
14733 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
14734          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
14735
14736 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
14737
14738 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
14739
14740 CREATE TABLE config.metabib_search_alias (
14741     alias       TEXT    PRIMARY KEY,
14742     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
14743     field       INT     REFERENCES config.metabib_field (id)
14744 );
14745
14746 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
14747 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
14748 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
14749 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
14750 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
14751 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
14752 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
14753 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
14754
14755 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
14756 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
14757 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
14758 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
14759 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
14760 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
14761 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
14762 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
14763 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
14764 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
14765 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
14766 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
14767 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
14768
14769 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
14770 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
14771 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
14772 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
14773 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
14774 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
14775 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
14776 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
14777
14778 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
14779 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
14780 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
14781 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
14782 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
14783 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
14784
14785 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
14786 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
14787 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
14788
14789 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
14790 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;
14791 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;
14792 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;
14793 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;
14794
14795 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
14796 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
14797 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
14798 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
14799
14800 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
14801 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
14802 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
14803 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
14804 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
14805 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
14806 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
14807 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
14808 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
14809 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
14810 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
14811 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
14812 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
14813 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
14814
14815 CREATE TABLE asset.opac_visible_copies (
14816   id        BIGINT primary key, -- copy id
14817   record    BIGINT,
14818   circ_lib  INTEGER
14819 );
14820 COMMENT ON TABLE asset.opac_visible_copies IS $$
14821 Materialized view of copies that are visible in the OPAC, used by
14822 search.query_parser_fts() to speed up OPAC visibility checks on large
14823 databases.  Contents are maintained by a set of triggers.
14824 $$;
14825 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
14826
14827 CREATE OR REPLACE FUNCTION search.query_parser_fts (
14828
14829     param_search_ou INT,
14830     param_depth     INT,
14831     param_query     TEXT,
14832     param_statuses  INT[],
14833     param_locations INT[],
14834     param_offset    INT,
14835     param_check     INT,
14836     param_limit     INT,
14837     metarecord      BOOL,
14838     staff           BOOL
14839  
14840 ) RETURNS SETOF search.search_result AS $func$
14841 DECLARE
14842
14843     current_res         search.search_result%ROWTYPE;
14844     search_org_list     INT[];
14845
14846     check_limit         INT;
14847     core_limit          INT;
14848     core_offset         INT;
14849     tmp_int             INT;
14850
14851     core_result         RECORD;
14852     core_cursor         REFCURSOR;
14853     core_rel_query      TEXT;
14854
14855     total_count         INT := 0;
14856     check_count         INT := 0;
14857     deleted_count       INT := 0;
14858     visible_count       INT := 0;
14859     excluded_count      INT := 0;
14860
14861 BEGIN
14862
14863     check_limit := COALESCE( param_check, 1000 );
14864     core_limit  := COALESCE( param_limit, 25000 );
14865     core_offset := COALESCE( param_offset, 0 );
14866
14867     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
14868
14869     IF param_search_ou > 0 THEN
14870         IF param_depth IS NOT NULL THEN
14871             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
14872         ELSE
14873             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
14874         END IF;
14875     ELSIF param_search_ou < 0 THEN
14876         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
14877     ELSIF param_search_ou = 0 THEN
14878         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
14879     END IF;
14880
14881     OPEN core_cursor FOR EXECUTE param_query;
14882
14883     LOOP
14884
14885         FETCH core_cursor INTO core_result;
14886         EXIT WHEN NOT FOUND;
14887         EXIT WHEN total_count >= core_limit;
14888
14889         total_count := total_count + 1;
14890
14891         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
14892
14893         check_count := check_count + 1;
14894
14895         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
14896         IF NOT FOUND THEN
14897             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
14898             deleted_count := deleted_count + 1;
14899             CONTINUE;
14900         END IF;
14901
14902         PERFORM 1
14903           FROM  biblio.record_entry b
14904                 JOIN config.bib_source s ON (b.source = s.id)
14905           WHERE s.transcendant
14906                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
14907
14908         IF FOUND THEN
14909             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
14910             visible_count := visible_count + 1;
14911
14912             current_res.id = core_result.id;
14913             current_res.rel = core_result.rel;
14914
14915             tmp_int := 1;
14916             IF metarecord THEN
14917                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
14918             END IF;
14919
14920             IF tmp_int = 1 THEN
14921                 current_res.record = core_result.records[1];
14922             ELSE
14923                 current_res.record = NULL;
14924             END IF;
14925
14926             RETURN NEXT current_res;
14927
14928             CONTINUE;
14929         END IF;
14930
14931         PERFORM 1
14932           FROM  asset.call_number cn
14933                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
14934                 JOIN asset.uri uri ON (map.uri = uri.id)
14935           WHERE NOT cn.deleted
14936                 AND cn.label = '##URI##'
14937                 AND uri.active
14938                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
14939                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
14940                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
14941           LIMIT 1;
14942
14943         IF FOUND THEN
14944             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
14945             visible_count := visible_count + 1;
14946
14947             current_res.id = core_result.id;
14948             current_res.rel = core_result.rel;
14949
14950             tmp_int := 1;
14951             IF metarecord THEN
14952                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
14953             END IF;
14954
14955             IF tmp_int = 1 THEN
14956                 current_res.record = core_result.records[1];
14957             ELSE
14958                 current_res.record = NULL;
14959             END IF;
14960
14961             RETURN NEXT current_res;
14962
14963             CONTINUE;
14964         END IF;
14965
14966         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
14967
14968             PERFORM 1
14969               FROM  asset.call_number cn
14970                     JOIN asset.copy cp ON (cp.call_number = cn.id)
14971               WHERE NOT cn.deleted
14972                     AND NOT cp.deleted
14973                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
14974                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
14975                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
14976               LIMIT 1;
14977
14978             IF NOT FOUND THEN
14979                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
14980                 excluded_count := excluded_count + 1;
14981                 CONTINUE;
14982             END IF;
14983
14984         END IF;
14985
14986         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
14987
14988             PERFORM 1
14989               FROM  asset.call_number cn
14990                     JOIN asset.copy cp ON (cp.call_number = cn.id)
14991               WHERE NOT cn.deleted
14992                     AND NOT cp.deleted
14993                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
14994                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
14995                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
14996               LIMIT 1;
14997
14998             IF NOT FOUND THEN
14999                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
15000                 excluded_count := excluded_count + 1;
15001                 CONTINUE;
15002             END IF;
15003
15004         END IF;
15005
15006         IF staff IS NULL OR NOT staff THEN
15007
15008             PERFORM 1
15009               FROM  asset.opac_visible_copies
15010               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15011                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15012               LIMIT 1;
15013
15014             IF NOT FOUND THEN
15015                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15016                 excluded_count := excluded_count + 1;
15017                 CONTINUE;
15018             END IF;
15019
15020         ELSE
15021
15022             PERFORM 1
15023               FROM  asset.call_number cn
15024                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15025                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15026               WHERE NOT cn.deleted
15027                     AND NOT cp.deleted
15028                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15029                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15030               LIMIT 1;
15031
15032             IF NOT FOUND THEN
15033
15034                 PERFORM 1
15035                   FROM  asset.call_number cn
15036                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15037                   LIMIT 1;
15038
15039                 IF FOUND THEN
15040                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15041                     excluded_count := excluded_count + 1;
15042                     CONTINUE;
15043                 END IF;
15044
15045             END IF;
15046
15047         END IF;
15048
15049         visible_count := visible_count + 1;
15050
15051         current_res.id = core_result.id;
15052         current_res.rel = core_result.rel;
15053
15054         tmp_int := 1;
15055         IF metarecord THEN
15056             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15057         END IF;
15058
15059         IF tmp_int = 1 THEN
15060             current_res.record = core_result.records[1];
15061         ELSE
15062             current_res.record = NULL;
15063         END IF;
15064
15065         RETURN NEXT current_res;
15066
15067         IF visible_count % 1000 = 0 THEN
15068             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15069         END IF;
15070
15071     END LOOP;
15072
15073     current_res.id = NULL;
15074     current_res.rel = NULL;
15075     current_res.record = NULL;
15076     current_res.total = total_count;
15077     current_res.checked = check_count;
15078     current_res.deleted = deleted_count;
15079     current_res.visible = visible_count;
15080     current_res.excluded = excluded_count;
15081
15082     CLOSE core_cursor;
15083
15084     RETURN NEXT current_res;
15085
15086 END;
15087 $func$ LANGUAGE PLPGSQL;
15088
15089 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15090 ALTER TABLE biblio.record_entry
15091          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15092          REFERENCES actor.org_unit (id)
15093          DEFERRABLE INITIALLY DEFERRED;
15094
15095 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15096
15097 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15098 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15099
15100 DROP VIEW auditor.biblio_record_entry_lifecycle;
15101
15102 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15103
15104 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15105         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15106 $$ LANGUAGE SQL STRICT IMMUTABLE;
15107
15108 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15109     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15110 $$ LANGUAGE SQL STRICT IMMUTABLE;
15111
15112 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15113     return lc(shift);
15114 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15115
15116 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15117     return uc(shift);
15118 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15119
15120 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15121     use Unicode::Normalize;
15122
15123     my $x = NFD(shift);
15124     $x =~ s/\pM+//go;
15125     return $x;
15126
15127 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15128
15129 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15130     use Unicode::Normalize;
15131
15132     my $x = NFC(shift);
15133     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15134     return $x;
15135
15136 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15137
15138 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15139 DECLARE
15140     setting RECORD;
15141     cur_org INT;
15142 BEGIN
15143     cur_org := org_id;
15144     LOOP
15145         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15146         IF FOUND THEN
15147             RETURN NEXT setting;
15148         END IF;
15149         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15150         EXIT WHEN cur_org IS NULL;
15151     END LOOP;
15152     RETURN;
15153 END;
15154 $$ LANGUAGE plpgsql STABLE;
15155
15156 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15157 DECLARE
15158     counter INT;
15159     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15160 BEGIN
15161
15162     SELECT  COUNT(*) INTO counter
15163       FROM  oils_xpath_table(
15164                 'id',
15165                 'marc',
15166                 'acq.lineitem',
15167                 '//*[@tag="' || tag || '"]',
15168                 'id=' || lineitem
15169             ) as t(i int,c text);
15170
15171     FOR i IN 1 .. counter LOOP
15172         FOR lida IN
15173             SELECT  *
15174               FROM  (   SELECT  id,i,t,v
15175                           FROM  oils_xpath_table(
15176                                     'id',
15177                                     'marc',
15178                                     'acq.lineitem',
15179                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15180                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15181                                     'id=' || lineitem
15182                                 ) as t(id int,t text,v text)
15183                     )x
15184         LOOP
15185             RETURN NEXT lida;
15186         END LOOP;
15187     END LOOP;
15188
15189     RETURN;
15190 END;
15191 $$ LANGUAGE PLPGSQL;
15192
15193 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15194 DECLARE
15195     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15196     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15197     result      config.i18n_core%ROWTYPE;
15198     fallback    TEXT;
15199     keyfield    TEXT := keyclass || '.' || keycol;
15200 BEGIN
15201
15202     -- Try the full locale
15203     SELECT  * INTO result
15204       FROM  config.i18n_core
15205       WHERE fq_field = keyfield
15206             AND identity_value = keyvalue
15207             AND translation = locale;
15208
15209     -- Try just the language
15210     IF NOT FOUND THEN
15211         SELECT  * INTO result
15212           FROM  config.i18n_core
15213           WHERE fq_field = keyfield
15214                 AND identity_value = keyvalue
15215                 AND translation = language;
15216     END IF;
15217
15218     -- Fall back to the string we passed in in the first place
15219     IF NOT FOUND THEN
15220     EXECUTE
15221             'SELECT ' ||
15222                 keycol ||
15223             ' FROM ' || keytable ||
15224             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15225                 INTO fallback;
15226         RETURN fallback;
15227     END IF;
15228
15229     RETURN result.string;
15230 END;
15231 $func$ LANGUAGE PLPGSQL STABLE;
15232
15233 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15234
15235 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15236
15237 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15238
15239 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15240     3, 1, 'delivered_but_lost',
15241     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15242
15243 CREATE TABLE config.global_flag (
15244     label   TEXT    NOT NULL
15245 ) INHERITS (config.internal_flag);
15246 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15247
15248 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
15249     VALUES (
15250         'cat.bib.use_id_for_tcn',
15251         oils_i18n_gettext(
15252             'cat.bib.use_id_for_tcn',
15253             'Cat: Use Internal ID for TCN Value',
15254             'cgf', 
15255             'label'
15256         )
15257     );
15258
15259 -- resolves performance issue noted by EG Indiana
15260
15261 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15262
15263 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15264
15265 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15266     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15267 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15268     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15269 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15270     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15271 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15272     (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 );
15273 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15274     (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 );
15275 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15276     (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 );
15277 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15278     (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 );
15279 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15280     (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 );
15281 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15282     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15283
15284 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15285  
15286
15287 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15288
15289 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15290 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15291 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15292 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15293 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15294 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15295
15296 CREATE TABLE metabib.identifier_field_entry (
15297         id              BIGSERIAL       PRIMARY KEY,
15298         source          BIGINT          NOT NULL,
15299         field           INT             NOT NULL,
15300         value           TEXT            NOT NULL,
15301         index_vector    tsvector        NOT NULL
15302 );
15303 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15304         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15305         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15306
15307 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15308 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15309     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15310 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15311
15312 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15313     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15314 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15315     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15316
15317 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15318     use Business::ISBN;
15319     use strict;
15320     use warnings;
15321
15322     # For each ISBN found in a single string containing a set of ISBNs:
15323     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15324     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15325
15326     my $input = shift;
15327     my $output = '';
15328
15329     foreach my $word (split(/\s/, $input)) {
15330         my $isbn = Business::ISBN->new($word);
15331
15332         # First check the checksum; if it is not valid, fix it and add the original
15333         # bad-checksum ISBN to the output
15334         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15335             $output .= $isbn->isbn() . " ";
15336             $isbn->fix_checksum();
15337         }
15338
15339         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15340         # and add the normalized original ISBN to the output
15341         if ($isbn && $isbn->is_valid()) {
15342             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15343             $output .= $isbn->isbn . " ";
15344
15345             # If we successfully converted the ISBN to its counterpart, add the
15346             # converted ISBN to the output as well
15347             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15348         }
15349     }
15350     return $output if $output;
15351
15352     # If there were no valid ISBNs, just return the raw input
15353     return $input;
15354 $func$ LANGUAGE PLPERLU;
15355
15356 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15357 /*
15358  * Copyright (C) 2010 Merrimack Valley Library Consortium
15359  * Jason Stephenson <jstephenson@mvlc.org>
15360  * Copyright (C) 2010 Laurentian University
15361  * Dan Scott <dscott@laurentian.ca>
15362  *
15363  * The translate_isbn1013 function takes an input ISBN and returns the
15364  * following in a single space-delimited string if the input ISBN is valid:
15365  *   - The normalized input ISBN (hyphens stripped)
15366  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15367  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15368  */
15369 $$;
15370
15371 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15372 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15373 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15374 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15375 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15376 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15377
15378 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15379         'ISBN 10/13 conversion',
15380         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15381         'translate_isbn1013',
15382         0
15383 );
15384
15385 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15386         'Replace',
15387         'Replace all occurences of first parameter in the string with the second parameter.',
15388         'replace',
15389         2
15390 );
15391
15392 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15393     SELECT  m.id, i.id, 1
15394       FROM  config.metabib_field m,
15395             config.index_normalizer i
15396       WHERE i.func IN ('first_word')
15397             AND m.id IN (18);
15398
15399 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15400     SELECT  m.id, i.id, 2
15401       FROM  config.metabib_field m,
15402             config.index_normalizer i
15403       WHERE i.func IN ('translate_isbn1013')
15404             AND m.id IN (18);
15405
15406 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15407     SELECT  m.id, i.id, $$['-','']$$
15408       FROM  config.metabib_field m,
15409             config.index_normalizer i
15410       WHERE i.func IN ('replace')
15411             AND m.id IN (19);
15412
15413 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15414     SELECT  m.id, i.id, $$[' ','']$$
15415       FROM  config.metabib_field m,
15416             config.index_normalizer i
15417       WHERE i.func IN ('replace')
15418             AND m.id IN (19);
15419
15420 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15421
15422 UPDATE  config.metabib_field_index_norm_map
15423   SET   params = REPLACE(params,E'\'','"')
15424   WHERE params IS NOT NULL AND params <> '';
15425
15426 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15427
15428 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15429
15430 ALTER TABLE config.circ_modifier
15431         ADD COLUMN avg_wait_time INTERVAL;
15432
15433 --CREATE TABLE actor.usr_password_reset (
15434 --  id SERIAL PRIMARY KEY,
15435 --  uuid TEXT NOT NULL, 
15436 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15437 --  request_time TIMESTAMP NOT NULL DEFAULT NOW(), 
15438 --  has_been_reset BOOL NOT NULL DEFAULT false
15439 --);
15440 --COMMENT ON TABLE actor.usr_password_reset IS $$
15441 --/*
15442 -- * Copyright (C) 2010 Laurentian University
15443 -- * Dan Scott <dscott@laurentian.ca>
15444 -- *
15445 -- * Self-serve password reset requests
15446 -- *
15447 -- * ****
15448 -- *
15449 -- * This program is free software; you can redistribute it and/or
15450 -- * modify it under the terms of the GNU General Public License
15451 -- * as published by the Free Software Foundation; either version 2
15452 -- * of the License, or (at your option) any later version.
15453 -- *
15454 -- * This program is distributed in the hope that it will be useful,
15455 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15456 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15457 -- * GNU General Public License for more details.
15458 -- */
15459 --$$;
15460 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15461 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15462 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15463 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15464
15465 -- Use the identifier search class tsconfig
15466 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15467 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15468     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15469     FOR EACH ROW
15470     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15471
15472 INSERT INTO config.global_flag (name,label,enabled)
15473     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15474 INSERT INTO config.global_flag (name,label,enabled)
15475     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15476
15477 -- turn a JSON scalar into an SQL TEXT value
15478 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15479     use JSON::XS;                    
15480     my $json = shift();
15481     my $txt;
15482     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15483     return undef if ($@);
15484     return $txt
15485 $f$ LANGUAGE PLPERLU;
15486
15487 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15488 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15489 DECLARE
15490     c               action.circulation%ROWTYPE;
15491     view_age        INTERVAL;
15492     usr_view_age    actor.usr_setting%ROWTYPE;
15493     usr_view_start  actor.usr_setting%ROWTYPE;
15494 BEGIN
15495     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15496     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15497
15498     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15499         -- User opted in and supplied a retention age
15500         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15501             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15502         ELSE
15503             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15504         END IF;
15505     ELSIF usr_view_start.value IS NOT NULL THEN
15506         -- User opted in
15507         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15508     ELSE
15509         -- User did not opt in
15510         RETURN;
15511     END IF;
15512
15513     FOR c IN
15514         SELECT  *
15515           FROM  action.circulation
15516           WHERE usr = usr_id
15517                 AND parent_circ IS NULL
15518                 AND xact_start > NOW() - view_age
15519           ORDER BY xact_start
15520     LOOP
15521         RETURN NEXT c;
15522     END LOOP;
15523
15524     RETURN;
15525 END;
15526 $func$ LANGUAGE PLPGSQL;
15527
15528 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15529 DECLARE
15530     usr_keep_age    actor.usr_setting%ROWTYPE;
15531     usr_keep_start  actor.usr_setting%ROWTYPE;
15532     org_keep_age    INTERVAL;
15533     org_keep_count  INT;
15534
15535     keep_age        INTERVAL;
15536
15537     target_acp      RECORD;
15538     circ_chain_head action.circulation%ROWTYPE;
15539     circ_chain_tail action.circulation%ROWTYPE;
15540
15541     purge_position  INT;
15542     count_purged    INT;
15543 BEGIN
15544
15545     count_purged := 0;
15546
15547     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15548
15549     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15550     IF org_keep_count IS NULL THEN
15551         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15552     END IF;
15553
15554     -- First, find copies with more than keep_count non-renewal circs
15555     FOR target_acp IN
15556         SELECT  target_copy,
15557                 COUNT(*) AS total_real_circs
15558           FROM  action.circulation
15559           WHERE parent_circ IS NULL
15560                 AND xact_finish IS NOT NULL
15561           GROUP BY target_copy
15562           HAVING COUNT(*) > org_keep_count
15563     LOOP
15564         purge_position := 0;
15565         -- And, for those, select circs that are finished and older than keep_age
15566         FOR circ_chain_head IN
15567             SELECT  *
15568               FROM  action.circulation
15569               WHERE target_copy = target_acp.target_copy
15570                     AND parent_circ IS NULL
15571               ORDER BY xact_start
15572         LOOP
15573
15574             -- Stop once we've purged enough circs to hit org_keep_count
15575             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15576
15577             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15578             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15579
15580             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15581             usr_keep_age.value := NULL;
15582             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15583
15584             usr_keep_start.value := NULL;
15585             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15586
15587             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15588                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15589                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15590                 ELSE
15591                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15592                 END IF;
15593             ELSIF usr_keep_start.value IS NOT NULL THEN
15594                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15595             ELSE
15596                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15597             END IF;
15598
15599             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15600
15601             -- We've passed the purging tests, purge the circ chain starting at the end
15602             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15603             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15604                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15605                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15606             END LOOP;
15607
15608             count_purged := count_purged + 1;
15609             purge_position := purge_position + 1;
15610
15611         END LOOP;
15612     END LOOP;
15613 END;
15614 $func$ LANGUAGE PLPGSQL;
15615
15616 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15617 DECLARE
15618     h               action.hold_request%ROWTYPE;
15619     view_age        INTERVAL;
15620     view_count      INT;
15621     usr_view_count  actor.usr_setting%ROWTYPE;
15622     usr_view_age    actor.usr_setting%ROWTYPE;
15623     usr_view_start  actor.usr_setting%ROWTYPE;
15624 BEGIN
15625     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15626     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15627     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15628
15629     FOR h IN
15630         SELECT  *
15631           FROM  action.hold_request
15632           WHERE usr = usr_id
15633                 AND fulfillment_time IS NULL
15634                 AND cancel_time IS NULL
15635           ORDER BY request_time DESC
15636     LOOP
15637         RETURN NEXT h;
15638     END LOOP;
15639
15640     IF usr_view_start.value IS NULL THEN
15641         RETURN;
15642     END IF;
15643
15644     IF usr_view_age.value IS NOT NULL THEN
15645         -- User opted in and supplied a retention age
15646         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15647             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15648         ELSE
15649             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15650         END IF;
15651     ELSE
15652         -- User opted in
15653         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15654     END IF;
15655
15656     IF usr_view_count.value IS NOT NULL THEN
15657         view_count := oils_json_to_text(usr_view_count.value)::INT;
15658     ELSE
15659         view_count := 1000;
15660     END IF;
15661
15662     -- show some fulfilled/canceled holds
15663     FOR h IN
15664         SELECT  *
15665           FROM  action.hold_request
15666           WHERE usr = usr_id
15667                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
15668                 AND request_time > NOW() - view_age
15669           ORDER BY request_time DESC
15670           LIMIT view_count
15671     LOOP
15672         RETURN NEXT h;
15673     END LOOP;
15674
15675     RETURN;
15676 END;
15677 $func$ LANGUAGE PLPGSQL;
15678
15679 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
15680
15681 DROP TABLE IF EXISTS serial.index_summary CASCADE;
15682
15683 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
15684
15685 DROP TABLE IF EXISTS serial.issuance CASCADE;
15686
15687 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
15688
15689 DROP TABLE IF EXISTS serial.subscription CASCADE;
15690
15691 CREATE TABLE asset.copy_template (
15692         id             SERIAL   PRIMARY KEY,
15693         owning_lib     INT      NOT NULL
15694                                 REFERENCES actor.org_unit (id)
15695                                 DEFERRABLE INITIALLY DEFERRED,
15696         creator        BIGINT   NOT NULL
15697                                 REFERENCES actor.usr (id)
15698                                 DEFERRABLE INITIALLY DEFERRED,
15699         editor         BIGINT   NOT NULL
15700                                 REFERENCES actor.usr (id)
15701                                 DEFERRABLE INITIALLY DEFERRED,
15702         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15703         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15704         name           TEXT     NOT NULL,
15705         -- columns above this point are attributes of the template itself
15706         -- columns after this point are attributes of the copy this template modifies/creates
15707         circ_lib       INT      REFERENCES actor.org_unit (id)
15708                                 DEFERRABLE INITIALLY DEFERRED,
15709         status         INT      REFERENCES config.copy_status (id)
15710                                 DEFERRABLE INITIALLY DEFERRED,
15711         location       INT      REFERENCES asset.copy_location (id)
15712                                 DEFERRABLE INITIALLY DEFERRED,
15713         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
15714                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
15715         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
15716                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
15717         age_protect    INT,
15718         circulate      BOOL,
15719         deposit        BOOL,
15720         ref            BOOL,
15721         holdable       BOOL,
15722         deposit_amount NUMERIC(6,2),
15723         price          NUMERIC(8,2),
15724         circ_modifier  TEXT,
15725         circ_as_type   TEXT,
15726         alert_message  TEXT,
15727         opac_visible   BOOL,
15728         floating       BOOL,
15729         mint_condition BOOL
15730 );
15731
15732 CREATE TABLE serial.subscription (
15733         id                     SERIAL       PRIMARY KEY,
15734         owning_lib             INT          NOT NULL DEFAULT 1
15735                                             REFERENCES actor.org_unit (id)
15736                                             ON DELETE SET NULL
15737                                             DEFERRABLE INITIALLY DEFERRED,
15738         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
15739         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
15740         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
15741                                             ON DELETE SET NULL
15742                                             DEFERRABLE INITIALLY DEFERRED,
15743         expected_date_offset   INTERVAL
15744         -- acquisitions/business-side tables link to here
15745 );
15746 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
15747 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
15748
15749 --at least one distribution per org_unit holding issues
15750 CREATE TABLE serial.distribution (
15751         id                    SERIAL  PRIMARY KEY,
15752         record_entry          BIGINT  REFERENCES serial.record_entry (id)
15753                                       ON DELETE SET NULL
15754                                       DEFERRABLE INITIALLY DEFERRED,
15755         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
15756                                           summary_method IS NULL
15757                                           OR summary_method IN ( 'add_to_sre',
15758                                           'merge_with_sre', 'use_sre_only',
15759                                           'use_sdist_only')),
15760         subscription          INT     NOT NULL
15761                                       REFERENCES serial.subscription (id)
15762                                                                   ON DELETE CASCADE
15763                                                                   DEFERRABLE INITIALLY DEFERRED,
15764         holding_lib           INT     NOT NULL
15765                                       REFERENCES actor.org_unit (id)
15766                                                                   DEFERRABLE INITIALLY DEFERRED,
15767         label                 TEXT    NOT NULL,
15768         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
15769                                       DEFERRABLE INITIALLY DEFERRED,
15770         receive_unit_template INT     REFERENCES asset.copy_template (id)
15771                                       DEFERRABLE INITIALLY DEFERRED,
15772         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
15773                                       DEFERRABLE INITIALLY DEFERRED,
15774         bind_unit_template    INT     REFERENCES asset.copy_template (id)
15775                                       DEFERRABLE INITIALLY DEFERRED,
15776         unit_label_prefix     TEXT,
15777         unit_label_suffix     TEXT
15778 );
15779 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
15780 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
15781
15782 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
15783
15784 CREATE TABLE serial.stream (
15785         id              SERIAL  PRIMARY KEY,
15786         distribution    INT     NOT NULL
15787                                 REFERENCES serial.distribution (id)
15788                                 ON DELETE CASCADE
15789                                 DEFERRABLE INITIALLY DEFERRED,
15790         routing_label   TEXT
15791 );
15792 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
15793
15794 CREATE UNIQUE INDEX label_once_per_dist
15795         ON serial.stream (distribution, routing_label)
15796         WHERE routing_label IS NOT NULL;
15797
15798 CREATE TABLE serial.routing_list_user (
15799         id             SERIAL       PRIMARY KEY,
15800         stream         INT          NOT NULL
15801                                     REFERENCES serial.stream
15802                                     ON DELETE CASCADE
15803                                     DEFERRABLE INITIALLY DEFERRED,
15804         pos            INT          NOT NULL DEFAULT 1,
15805         reader         INT          REFERENCES actor.usr
15806                                     ON DELETE CASCADE
15807                                     DEFERRABLE INITIALLY DEFERRED,
15808         department     TEXT,
15809         note           TEXT,
15810         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
15811         CONSTRAINT reader_or_dept CHECK
15812         (
15813             -- Recipient is a person or a department, but not both
15814                 (reader IS NOT NULL AND department IS NULL) OR
15815                 (reader IS NULL AND department IS NOT NULL)
15816         )
15817 );
15818 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
15819 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
15820
15821 CREATE TABLE serial.caption_and_pattern (
15822         id           SERIAL       PRIMARY KEY,
15823         subscription INT          NOT NULL REFERENCES serial.subscription (id)
15824                                   ON DELETE CASCADE
15825                                   DEFERRABLE INITIALLY DEFERRED,
15826         type         TEXT         NOT NULL
15827                                   CONSTRAINT cap_type CHECK ( type in
15828                                   ( 'basic', 'supplement', 'index' )),
15829         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
15830         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
15831         end_date     TIMESTAMP WITH TIME ZONE,
15832         active       BOOL         NOT NULL DEFAULT FALSE,
15833         pattern_code TEXT         NOT NULL,       -- must contain JSON
15834         enum_1       TEXT,
15835         enum_2       TEXT,
15836         enum_3       TEXT,
15837         enum_4       TEXT,
15838         enum_5       TEXT,
15839         enum_6       TEXT,
15840         chron_1      TEXT,
15841         chron_2      TEXT,
15842         chron_3      TEXT,
15843         chron_4      TEXT,
15844         chron_5      TEXT
15845 );
15846 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
15847
15848 CREATE TABLE serial.issuance (
15849         id              SERIAL    PRIMARY KEY,
15850         creator         INT       NOT NULL
15851                                   REFERENCES actor.usr (id)
15852                                                           DEFERRABLE INITIALLY DEFERRED,
15853         editor          INT       NOT NULL
15854                                   REFERENCES actor.usr (id)
15855                                   DEFERRABLE INITIALLY DEFERRED,
15856         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15857         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15858         subscription    INT       NOT NULL
15859                                   REFERENCES serial.subscription (id)
15860                                   ON DELETE CASCADE
15861                                   DEFERRABLE INITIALLY DEFERRED,
15862         label           TEXT,
15863         date_published  TIMESTAMP WITH TIME ZONE,
15864         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
15865                               DEFERRABLE INITIALLY DEFERRED,
15866         holding_code    TEXT,
15867         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
15868                                   (
15869                                       holding_type IS NULL
15870                                       OR holding_type IN ('basic','supplement','index')
15871                                   ),
15872         holding_link_id INT
15873         -- TODO: add columns for separate enumeration/chronology values
15874 );
15875 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
15876 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
15877 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
15878
15879 CREATE TABLE serial.unit (
15880         label           TEXT,
15881         label_sort_key  TEXT,
15882         contents        TEXT    NOT NULL
15883 ) INHERITS (asset.copy);
15884 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
15885 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
15886 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
15887 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
15888 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
15889
15890 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
15891
15892 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
15893
15894 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
15895
15896 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
15897
15898 CREATE TABLE serial.item (
15899         id              SERIAL  PRIMARY KEY,
15900         creator         INT     NOT NULL
15901                                 REFERENCES actor.usr (id)
15902                                 DEFERRABLE INITIALLY DEFERRED,
15903         editor          INT     NOT NULL
15904                                 REFERENCES actor.usr (id)
15905                                 DEFERRABLE INITIALLY DEFERRED,
15906         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15907         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15908         issuance        INT     NOT NULL
15909                                 REFERENCES serial.issuance (id)
15910                                 ON DELETE CASCADE
15911                                 DEFERRABLE INITIALLY DEFERRED,
15912         stream          INT     NOT NULL
15913                                 REFERENCES serial.stream (id)
15914                                 ON DELETE CASCADE
15915                                 DEFERRABLE INITIALLY DEFERRED,
15916         unit            INT     REFERENCES serial.unit (id)
15917                                 ON DELETE SET NULL
15918                                 DEFERRABLE INITIALLY DEFERRED,
15919         uri             INT     REFERENCES asset.uri (id)
15920                                 ON DELETE SET NULL
15921                                 DEFERRABLE INITIALLY DEFERRED,
15922         date_expected   TIMESTAMP WITH TIME ZONE,
15923         date_received   TIMESTAMP WITH TIME ZONE,
15924         status          TEXT    CONSTRAINT valid_status CHECK (
15925                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
15926                                'Expected', 'Not Held', 'Not Published', 'Received'))
15927                             DEFAULT 'Expected',
15928         shadowed        BOOL    NOT NULL DEFAULT FALSE
15929 );
15930 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
15931 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
15932 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
15933 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
15934 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
15935 CREATE INDEX serial_item_status_idx ON serial.item (status);
15936
15937 CREATE TABLE serial.item_note (
15938         id          SERIAL  PRIMARY KEY,
15939         item        INT     NOT NULL
15940                             REFERENCES serial.item (id)
15941                             ON DELETE CASCADE
15942                             DEFERRABLE INITIALLY DEFERRED,
15943         creator     INT     NOT NULL
15944                             REFERENCES actor.usr (id)
15945                             DEFERRABLE INITIALLY DEFERRED,
15946         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15947         pub         BOOL    NOT NULL    DEFAULT FALSE,
15948         title       TEXT    NOT NULL,
15949         value       TEXT    NOT NULL
15950 );
15951 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
15952
15953 CREATE TABLE serial.basic_summary (
15954         id                  SERIAL  PRIMARY KEY,
15955         distribution        INT     NOT NULL
15956                                     REFERENCES serial.distribution (id)
15957                                     ON DELETE CASCADE
15958                                     DEFERRABLE INITIALLY DEFERRED,
15959         generated_coverage  TEXT    NOT NULL,
15960         textual_holdings    TEXT,
15961         show_generated      BOOL    NOT NULL DEFAULT TRUE
15962 );
15963 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
15964
15965 CREATE TABLE serial.supplement_summary (
15966         id                  SERIAL  PRIMARY KEY,
15967         distribution        INT     NOT NULL
15968                                     REFERENCES serial.distribution (id)
15969                                     ON DELETE CASCADE
15970                                     DEFERRABLE INITIALLY DEFERRED,
15971         generated_coverage  TEXT    NOT NULL,
15972         textual_holdings    TEXT,
15973         show_generated      BOOL    NOT NULL DEFAULT TRUE
15974 );
15975 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
15976
15977 CREATE TABLE serial.index_summary (
15978         id                  SERIAL  PRIMARY KEY,
15979         distribution        INT     NOT NULL
15980                                     REFERENCES serial.distribution (id)
15981                                     ON DELETE CASCADE
15982                                     DEFERRABLE INITIALLY DEFERRED,
15983         generated_coverage  TEXT    NOT NULL,
15984         textual_holdings    TEXT,
15985         show_generated      BOOL    NOT NULL DEFAULT TRUE
15986 );
15987 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
15988
15989 -- 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.
15990
15991 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
15992 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
15993
15994 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
15995 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;
15996
15997 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
15998 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
15999
16000 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
16001 RETURNS INTEGER AS $$
16002 BEGIN
16003         RETURN EXTRACT( EPOCH FROM interval_val );
16004 END;
16005 $$ LANGUAGE plpgsql;
16006
16007 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
16008 RETURNS INTEGER AS $$
16009 BEGIN
16010         RETURN config.interval_to_seconds( interval_string::INTERVAL );
16011 END;
16012 $$ LANGUAGE plpgsql;
16013
16014 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
16015     'temp',
16016     oils_i18n_gettext(
16017         'temp',
16018         'Temporary bucket which gets deleted after use.',
16019         'cbrebt',
16020         'label'
16021     )
16022 );
16023
16024 -- 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.
16025
16026 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
16027 BEGIN
16028
16029     IF xml_is_well_formed(NEW.marc) THEN
16030         RETURN NEW;
16031     ELSE
16032         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16033     END IF;
16034     
16035 END;
16036 $func$ LANGUAGE PLPGSQL;
16037
16038 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();
16039
16040 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();
16041
16042 ALTER TABLE serial.record_entry
16043         ALTER COLUMN marc DROP NOT NULL;
16044
16045 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16046 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16047 <xsl:stylesheet
16048     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16049     xmlns:marc="http://www.loc.gov/MARC21/slim"
16050     version="1.0">
16051 <!--
16052 Copyright (C) 2010  Equinox Software, Inc.
16053 Galen Charlton <gmc@esilibrary.cOM.
16054
16055 This program is free software; you can redistribute it and/or
16056 modify it under the terms of the GNU General Public License
16057 as published by the Free Software Foundation; either version 2
16058 of the License, or (at your option) any later version.
16059
16060 This program is distributed in the hope that it will be useful,
16061 but WITHOUT ANY WARRANTY; without even the implied warranty of
16062 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16063 GNU General Public License for more details.
16064
16065 marc21_expand_880.xsl - stylesheet used during indexing to
16066                         map alternative graphical representations
16067                         of MARC fields stored in 880 fields
16068                         to the corresponding tag name and value.
16069
16070 For example, if a MARC record for a Chinese book has
16071
16072 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16073 880.00 $6 245-01/$1 $a八十三年短篇小說選
16074
16075 this stylesheet will transform it to the equivalent of
16076
16077 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16078 245.00 $6 245-01/$1 $a八十三年短篇小說選
16079
16080 -->
16081     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16082
16083     <xsl:template match="@*|node()">
16084         <xsl:copy>
16085             <xsl:apply-templates select="@*|node()"/>
16086         </xsl:copy>
16087     </xsl:template>
16088
16089     <xsl:template match="//marc:datafield[@tag='880']">
16090         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16091             <marc:datafield>
16092                 <xsl:attribute name="tag">
16093                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16094                 </xsl:attribute>
16095                 <xsl:attribute name="ind1">
16096                     <xsl:value-of select="@ind1" />
16097                 </xsl:attribute>
16098                 <xsl:attribute name="ind2">
16099                     <xsl:value-of select="@ind2" />
16100                 </xsl:attribute>
16101                 <xsl:apply-templates />
16102             </marc:datafield>
16103         </xsl:if>
16104     </xsl:template>
16105     
16106 </xsl:stylesheet>$$);
16107
16108 -- Splitting the ingest trigger up into little bits
16109
16110 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16111     flag INTEGER PRIMARY KEY
16112 ) ON COMMIT DROP;
16113 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16114
16115 -- cause failure if either of the tables we want to drop have rows
16116 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16117 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16118
16119 DROP TABLE IF EXISTS asset.copy_transparency_map;
16120 DROP TABLE IF EXISTS asset.copy_transparency;
16121
16122 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16123
16124 -- We won't necessarily use all of these, but they are here for completeness.
16125 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16126 -- Values are the EDI code value + 1000
16127
16128 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16129 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16130 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16131 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16132 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16133 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16134 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16135 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16136 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16137 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16138 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16139 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16140 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16141 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16142 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16143 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16144 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16145 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16146 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16147 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16148 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16149 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16150 ('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).'),
16151 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16152 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16153 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16154 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16155 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16156 ('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.'),
16157 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16158 ('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.'),
16159 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16160 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16161 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16162 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16163 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16164 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16165 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16166 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16167 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16168 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16169 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16170 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16171 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16172 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16173 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16174 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16175 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16176 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16177 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16178 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16179 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16180 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16181 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16182 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16183 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16184 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16185 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16186 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16187 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16188 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16189 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16190 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16191 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16192 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16193 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16194 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16195 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16196 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16197 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16198 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16199 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16200 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16201 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16202 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16203 ('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).'),
16204 ('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).'),
16205 ('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).'),
16206 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16207 ('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).'),
16208 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16209 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16210 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16211 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16212 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16213 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16214 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16215 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16216 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16217 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16218 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16219 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16220 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16221 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16222 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16223 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16224 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16225 ('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.'),
16226 ('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.'),
16227 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16228 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16229 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16230 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16231 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16232 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16233 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16234 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16235 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16236 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16237 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16238 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16239 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16240 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16241 ('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.'),
16242 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16243 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16244
16245 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16246     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16247  
16248 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16249  
16250 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16251         'Remove Parenthesized Substring',
16252         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16253         'remove_paren_substring',
16254         0
16255 );
16256
16257 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16258         'Trim Surrounding Space',
16259         'Trim leading and trailing spaces from extracted text.',
16260         'btrim',
16261         0
16262 );
16263
16264 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16265     SELECT  m.id,
16266             i.id,
16267             -2
16268       FROM  config.metabib_field m,
16269             config.index_normalizer i
16270       WHERE i.func IN ('remove_paren_substring')
16271             AND m.id IN (26);
16272
16273 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16274     SELECT  m.id,
16275             i.id,
16276             -1
16277       FROM  config.metabib_field m,
16278             config.index_normalizer i
16279       WHERE i.func IN ('btrim')
16280             AND m.id IN (26);
16281
16282 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16283 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16284 DECLARE
16285     dyn_profile     vandelay.compile_profile%ROWTYPE;
16286     replace_rule    TEXT;
16287     tmp_marc        TEXT;
16288     trgt_marc        TEXT;
16289     tmpl_marc        TEXT;
16290     match_count     INT;
16291 BEGIN
16292
16293     IF target_marc IS NULL OR template_marc IS NULL THEN
16294         -- RAISE NOTICE 'no marc for target or template record';
16295         RETURN NULL;
16296     END IF;
16297
16298     dyn_profile := vandelay.compile_profile( template_marc );
16299
16300     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16301         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16302         RETURN NULL;
16303     END IF;
16304
16305     IF dyn_profile.replace_rule <> '' THEN
16306         trgt_marc = target_marc;
16307         tmpl_marc = template_marc;
16308         replace_rule = dyn_profile.replace_rule;
16309     ELSE
16310         tmp_marc = target_marc;
16311         trgt_marc = template_marc;
16312         tmpl_marc = tmp_marc;
16313         replace_rule = dyn_profile.preserve_rule;
16314     END IF;
16315
16316     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16317
16318 END;
16319 $$ LANGUAGE PLPGSQL;
16320
16321 -- Function to generate an ephemeral overlay template from an authority record
16322 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16323
16324     use MARC::Record;
16325     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16326
16327     my $xml = shift;
16328     my $r = MARC::Record->new_from_xml( $xml );
16329
16330     return undef unless ($r);
16331
16332     my $id = shift() || $r->subfield( '901' => 'c' );
16333     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16334     return undef unless ($id); # We need an ID!
16335
16336     my $tmpl = MARC::Record->new();
16337
16338     my @rule_fields;
16339     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16340
16341         my $tag = $field->tag;
16342         my $i1 = $field->indicator(1);
16343         my $i2 = $field->indicator(2);
16344         my $sf = join '', map { $_->[0] } $field->subfields;
16345         my @data = map { @$_ } $field->subfields;
16346
16347         my @replace_them;
16348
16349         # Map the authority field to bib fields it can control.
16350         if ($tag >= 100 and $tag <= 111) {       # names
16351             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16352         } elsif ($tag eq '130') {                # uniform title
16353             @replace_them = qw/130 240 440 730 830/;
16354         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16355             @replace_them = ($tag + 500);
16356         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16357             @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/;
16358         } else {
16359             next;
16360         }
16361
16362         # Dummy up the bib-side data
16363         $tmpl->append_fields(
16364             map {
16365                 MARC::Field->new( $_, $i1, $i2, @data )
16366             } @replace_them
16367         );
16368
16369         # Construct some 'replace' rules
16370         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16371     }
16372
16373     # Insert the replace rules into the template
16374     $tmpl->append_fields(
16375         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16376     );
16377
16378     $xml = $tmpl->as_xml_record;
16379     $xml =~ s/^<\?.+?\?>$//mo;
16380     $xml =~ s/\n//sgo;
16381     $xml =~ s/>\s+</></sgo;
16382
16383     return $xml;
16384
16385 $func$ LANGUAGE PLPERLU;
16386
16387 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16388     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16389 $func$ LANGUAGE SQL;
16390
16391 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16392     SELECT authority.generate_overlay_template( $1, NULL );
16393 $func$ LANGUAGE SQL;
16394
16395 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16396 DELETE FROM config.metabib_field WHERE id = 26;
16397
16398 -- Making this a global_flag (UI accessible) instead of an internal_flag
16399 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16400     VALUES (
16401         'ingest.disable_authority_linking',
16402         oils_i18n_gettext(
16403             'ingest.disable_authority_linking',
16404             'Authority Automation: Disable bib-authority link tracking',
16405             'cgf', 
16406             'label'
16407         )
16408     );
16409 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16410 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16411
16412 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16413     VALUES (
16414         'ingest.disable_authority_auto_update',
16415         oils_i18n_gettext(
16416             'ingest.disable_authority_auto_update',
16417             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16418             'cgf', 
16419             'label'
16420         )
16421     );
16422
16423 -- Enable automated ingest of authority records; just insert the row into
16424 -- authority.record_entry and authority.full_rec will automatically be populated
16425
16426 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16427     UPDATE  biblio.record_entry
16428       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16429       WHERE id = $2;
16430     SELECT $1;
16431 $func$ LANGUAGE SQL;
16432
16433 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16434     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16435 $func$ LANGUAGE SQL;
16436
16437 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16438
16439 use MARC::Record;
16440 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16441
16442 my $xml = shift;
16443 my $r = MARC::Record->new_from_xml( $xml );
16444
16445 return_next( { tag => 'LDR', value => $r->leader } );
16446
16447 for my $f ( $r->fields ) {
16448     if ($f->is_control_field) {
16449         return_next({ tag => $f->tag, value => $f->data });
16450     } else {
16451         for my $s ($f->subfields) {
16452             return_next({
16453                 tag      => $f->tag,
16454                 ind1     => $f->indicator(1),
16455                 ind2     => $f->indicator(2),
16456                 subfield => $s->[0],
16457                 value    => $s->[1]
16458             });
16459
16460         }
16461     }
16462 }
16463
16464 return undef;
16465
16466 $func$ LANGUAGE PLPERLU;
16467
16468 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16469 DECLARE
16470     auth    authority.record_entry%ROWTYPE;
16471     output    authority.full_rec%ROWTYPE;
16472     field    RECORD;
16473 BEGIN
16474     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16475
16476     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16477         output.record := rid;
16478         output.ind1 := field.ind1;
16479         output.ind2 := field.ind2;
16480         output.tag := field.tag;
16481         output.subfield := field.subfield;
16482         IF field.subfield IS NOT NULL THEN
16483             output.value := naco_normalize(field.value, field.subfield);
16484         ELSE
16485             output.value := field.value;
16486         END IF;
16487
16488         CONTINUE WHEN output.value IS NULL;
16489
16490         RETURN NEXT output;
16491     END LOOP;
16492 END;
16493 $func$ LANGUAGE PLPGSQL;
16494
16495 -- authority.rec_descriptor appears to be unused currently
16496 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16497 BEGIN
16498     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16499 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16500 --        SELECT  auth_id, ;
16501
16502     RETURN;
16503 END;
16504 $func$ LANGUAGE PLPGSQL;
16505
16506 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16507 BEGIN
16508     DELETE FROM authority.full_rec WHERE record = auth_id;
16509     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16510         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16511
16512     RETURN;
16513 END;
16514 $func$ LANGUAGE PLPGSQL;
16515
16516 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16517 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16518 BEGIN
16519
16520     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16521         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16522           -- Should remove matching $0 from controlled fields at the same time?
16523         RETURN NEW; -- and we're done
16524     END IF;
16525
16526     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16527         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16528
16529         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16530             RETURN NEW;
16531         END IF;
16532     END IF;
16533
16534     -- Flatten and insert the afr data
16535     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16536     IF NOT FOUND THEN
16537         PERFORM authority.reingest_authority_full_rec(NEW.id);
16538 -- authority.rec_descriptor is not currently used
16539 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16540 --        IF NOT FOUND THEN
16541 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16542 --        END IF;
16543     END IF;
16544
16545     RETURN NEW;
16546 END;
16547 $func$ LANGUAGE PLPGSQL;
16548
16549 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 ();
16550
16551 -- Some records manage to get XML namespace declarations into each element,
16552 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16553 -- This broke the old maintain_901(), so we'll make the regex more robust
16554
16555 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16556 BEGIN
16557     -- Remove any existing 901 fields before we insert the authoritative one
16558     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16559     IF TG_TABLE_SCHEMA = 'biblio' THEN
16560         NEW.marc := REGEXP_REPLACE(
16561             NEW.marc,
16562             E'(</(?:[^:]*?:)?record>)',
16563             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16564                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16565                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16566                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16567                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16568                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16569                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16570              E'</datafield>\\1'
16571         );
16572     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16573         NEW.marc := REGEXP_REPLACE(
16574             NEW.marc,
16575             E'(</(?:[^:]*?:)?record>)',
16576             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16577                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16578                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16579              E'</datafield>\\1'
16580         );
16581     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16582         NEW.marc := REGEXP_REPLACE(
16583             NEW.marc,
16584             E'(</(?:[^:]*?:)?record>)',
16585             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16586                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16587                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16588                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16589                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16590              E'</datafield>\\1'
16591         );
16592     ELSE
16593         NEW.marc := REGEXP_REPLACE(
16594             NEW.marc,
16595             E'(</(?:[^:]*?:)?record>)',
16596             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16597                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16598                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16599              E'</datafield>\\1'
16600         );
16601     END IF;
16602
16603     RETURN NEW;
16604 END;
16605 $func$ LANGUAGE PLPGSQL;
16606
16607 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16608 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16609 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16610  
16611 -- In booking, elbow room defines:
16612 --  a) how far in the future you must make a reservation on a given item if
16613 --      that item will have to transit somewhere to fulfill the reservation.
16614 --  b) how soon a reservation must be starting for the reserved item to
16615 --      be op-captured by the checkin interface.
16616
16617 -- We don't want to clobber any default_elbow room at any level:
16618
16619 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16620 DECLARE
16621     existing    actor.org_unit_setting%ROWTYPE;
16622 BEGIN
16623     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16624     IF NOT FOUND THEN
16625         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16626             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16627             'circ.booking_reservation.default_elbow_room',
16628             '"1 day"'
16629         );
16630         RETURN 1;
16631     END IF;
16632     RETURN 0;
16633 END;
16634 $$ LANGUAGE plpgsql;
16635
16636 SELECT pg_temp.default_elbow();
16637
16638 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16639
16640 -- returns the distinct set of target copy IDs from a user's visible circulation history
16641 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16642     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16643 $$ LANGUAGE SQL;
16644
16645 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
16646 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
16647 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
16648 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
16649
16650 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
16651
16652 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
16653 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
16654
16655 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16656     VALUES (
16657         'cat.maintain_control_numbers',
16658         oils_i18n_gettext(
16659             'cat.maintain_control_numbers',
16660             'Cat: Maintain 001/003/035 according to the MARC21 specification',
16661             'cgf', 
16662             'label'
16663         )
16664     );
16665
16666 INSERT INTO config.global_flag (name, label, enabled)
16667     VALUES (
16668         'circ.holds.empty_issuance_ok',
16669         oils_i18n_gettext(
16670             'circ.holds.empty_issuance_ok',
16671             'Holds: Allow holds on empty issuances',
16672             'cgf',
16673             'label'
16674         ),
16675         TRUE
16676     );
16677
16678 INSERT INTO config.global_flag (name, label, enabled)
16679     VALUES (
16680         'circ.holds.usr_not_requestor',
16681         oils_i18n_gettext(
16682             'circ.holds.usr_not_requestor',
16683             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
16684             'cgf',
16685             'label'
16686         ),
16687         TRUE
16688     );
16689
16690 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
16691 use strict;
16692 use MARC::Record;
16693 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16694 use Encode;
16695 use Unicode::Normalize;
16696
16697 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
16698 my $schema = $_TD->{table_schema};
16699 my $rec_id = $_TD->{new}{id};
16700
16701 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
16702 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
16703 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
16704     return;
16705 }
16706
16707 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
16708 my $ou_cni = 'EVRGRN';
16709
16710 my $owner;
16711 if ($schema eq 'serial') {
16712     $owner = $_TD->{new}{owning_lib};
16713 } else {
16714     # are.owner and bre.owner can be null, so fall back to the consortial setting
16715     $owner = $_TD->{new}{owner} || 1;
16716 }
16717
16718 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
16719 if ($ous_rv->{processed}) {
16720     $ou_cni = $ous_rv->{rows}[0]->{value};
16721     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
16722 } else {
16723     # Fall back to the shortname of the OU if there was no OU setting
16724     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
16725     if ($ous_rv->{processed}) {
16726         $ou_cni = $ous_rv->{rows}[0]->{shortname};
16727     }
16728 }
16729
16730 my ($create, $munge) = (0, 0);
16731 my ($orig_001, $orig_003) = ('', '');
16732
16733 # Incoming MARC records may have multiple 001s or 003s, despite the spec
16734 my @control_ids = $record->field('003');
16735 my @scns = $record->field('035');
16736
16737 foreach my $id_field ('001', '003') {
16738     my $spec_value;
16739     my @controls = $record->field($id_field);
16740
16741     if ($id_field eq '001') {
16742         $spec_value = $rec_id;
16743     } else {
16744         $spec_value = $ou_cni;
16745     }
16746
16747     # Create the 001/003 if none exist
16748     if (scalar(@controls) == 0) {
16749         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
16750         $create = 1;
16751     } elsif (scalar(@controls) > 1) {
16752         # Do we already have the right 001/003 value in the existing set?
16753         unless (grep $_->data() eq $spec_value, @controls) {
16754             $munge = 1;
16755         }
16756
16757         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
16758         foreach my $control (@controls) {
16759             unless ($control->data() eq $spec_value) {
16760                 $record->delete_field($control);
16761             }
16762         }
16763     } else {
16764         # Only one field; check to see if we need to munge it
16765         unless (grep $_->data() eq $spec_value, @controls) {
16766             $munge = 1;
16767         }
16768     }
16769 }
16770
16771 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
16772 if ($munge) {
16773     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
16774
16775     # Do not create duplicate 035 fields
16776     unless (grep $_->subfield('a') eq $scn, @scns) {
16777         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
16778     }
16779 }
16780
16781 # Set the 001/003 and update the MARC
16782 if ($create or $munge) {
16783     $record->field('001')->data($rec_id);
16784     $record->field('003')->data($ou_cni);
16785
16786     my $xml = $record->as_xml_record();
16787     $xml =~ s/\n//sgo;
16788     $xml =~ s/^<\?xml.+\?\s*>//go;
16789     $xml =~ s/>\s+</></go;
16790     $xml =~ s/\p{Cc}//go;
16791
16792     # Embed a version of OpenILS::Application::AppUtils->entityize()
16793     # to avoid having to set PERL5LIB for PostgreSQL as well
16794
16795     # If we are going to convert non-ASCII characters to XML entities,
16796     # we had better be dealing with a UTF8 string to begin with
16797     $xml = decode_utf8($xml);
16798
16799     $xml = NFC($xml);
16800
16801     # Convert raw ampersands to entities
16802     $xml =~ s/&(?!\S+;)/&amp;/gso;
16803
16804     # Convert Unicode characters to entities
16805     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
16806
16807     $xml =~ s/[\x00-\x1f]//go;
16808     $_TD->{new}{marc} = $xml;
16809
16810     return "MODIFY";
16811 }
16812
16813 return;
16814 $func$ LANGUAGE PLPERLU;
16815
16816 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16817 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16818 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16819
16820 INSERT INTO metabib.facet_entry (source, field, value)
16821     SELECT source, field, value FROM (
16822         SELECT * FROM metabib.author_field_entry
16823             UNION ALL
16824         SELECT * FROM metabib.keyword_field_entry
16825             UNION ALL
16826         SELECT * FROM metabib.identifier_field_entry
16827             UNION ALL
16828         SELECT * FROM metabib.title_field_entry
16829             UNION ALL
16830         SELECT * FROM metabib.subject_field_entry
16831             UNION ALL
16832         SELECT * FROM metabib.series_field_entry
16833         )x
16834     WHERE x.index_vector = '';
16835         
16836 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
16837 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
16838 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
16839 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
16840 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
16841 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
16842
16843 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
16844 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
16845 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
16846
16847 -- copy OPAC visibility materialized view
16848 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
16849
16850     TRUNCATE TABLE asset.opac_visible_copies;
16851
16852     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16853     SELECT  cp.id, cp.circ_lib, cn.record
16854     FROM  asset.copy cp
16855         JOIN asset.call_number cn ON (cn.id = cp.call_number)
16856         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
16857         JOIN asset.copy_location cl ON (cp.location = cl.id)
16858         JOIN config.copy_status cs ON (cp.status = cs.id)
16859         JOIN biblio.record_entry b ON (cn.record = b.id)
16860     WHERE NOT cp.deleted
16861         AND NOT cn.deleted
16862         AND NOT b.deleted
16863         AND cs.opac_visible
16864         AND cl.opac_visible
16865         AND cp.opac_visible
16866         AND a.opac_visible;
16867
16868 $$ LANGUAGE SQL;
16869 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
16870 Rebuild the copy OPAC visibility cache.  Useful during migrations.
16871 $$;
16872
16873 -- and actually populate the table
16874 SELECT asset.refresh_opac_visible_copies_mat_view();
16875
16876 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
16877 DECLARE
16878     add_query       TEXT;
16879     remove_query    TEXT;
16880     do_add          BOOLEAN := false;
16881     do_remove       BOOLEAN := false;
16882 BEGIN
16883     add_query := $$
16884             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16885                 SELECT  cp.id, cp.circ_lib, cn.record
16886                   FROM  asset.copy cp
16887                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
16888                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
16889                         JOIN asset.copy_location cl ON (cp.location = cl.id)
16890                         JOIN config.copy_status cs ON (cp.status = cs.id)
16891                         JOIN biblio.record_entry b ON (cn.record = b.id)
16892                   WHERE NOT cp.deleted
16893                         AND NOT cn.deleted
16894                         AND NOT b.deleted
16895                         AND cs.opac_visible
16896                         AND cl.opac_visible
16897                         AND cp.opac_visible
16898                         AND a.opac_visible
16899     $$;
16900  
16901     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
16902
16903     IF TG_OP = 'INSERT' THEN
16904
16905         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
16906             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
16907             EXECUTE add_query;
16908         END IF;
16909
16910         RETURN NEW;
16911
16912     END IF;
16913
16914     -- handle items first, since with circulation activity
16915     -- their statuses change frequently
16916     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
16917
16918         IF OLD.location    <> NEW.location OR
16919            OLD.call_number <> NEW.call_number OR
16920            OLD.status      <> NEW.status OR
16921            OLD.circ_lib    <> NEW.circ_lib THEN
16922             -- any of these could change visibility, but
16923             -- we'll save some queries and not try to calculate
16924             -- the change directly
16925             do_remove := true;
16926             do_add := true;
16927         ELSE
16928
16929             IF OLD.deleted <> NEW.deleted THEN
16930                 IF NEW.deleted THEN
16931                     do_remove := true;
16932                 ELSE
16933                     do_add := true;
16934                 END IF;
16935             END IF;
16936
16937             IF OLD.opac_visible <> NEW.opac_visible THEN
16938                 IF OLD.opac_visible THEN
16939                     do_remove := true;
16940                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
16941                                         -- is also marked opac_visible
16942                     do_add := true;
16943                 END IF;
16944             END IF;
16945
16946         END IF;
16947
16948         IF do_remove THEN
16949             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
16950         END IF;
16951         IF do_add THEN
16952             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
16953             EXECUTE add_query;
16954         END IF;
16955
16956         RETURN NEW;
16957
16958     END IF;
16959
16960     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
16961  
16962         IF OLD.deleted AND NEW.deleted THEN -- do nothing
16963
16964             RETURN NEW;
16965  
16966         ELSIF NEW.deleted THEN -- remove rows
16967  
16968             IF TG_TABLE_NAME = 'call_number' THEN
16969                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
16970             ELSIF TG_TABLE_NAME = 'record_entry' THEN
16971                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
16972             END IF;
16973  
16974             RETURN NEW;
16975  
16976         ELSIF OLD.deleted THEN -- add rows
16977  
16978             IF TG_TABLE_NAME IN ('copy','unit') THEN
16979                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
16980             ELSIF TG_TABLE_NAME = 'call_number' THEN
16981                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
16982             ELSIF TG_TABLE_NAME = 'record_entry' THEN
16983                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
16984             END IF;
16985  
16986             EXECUTE add_query;
16987             RETURN NEW;
16988  
16989         END IF;
16990  
16991     END IF;
16992
16993     IF TG_TABLE_NAME = 'call_number' THEN
16994
16995         IF OLD.record <> NEW.record THEN
16996             -- call number is linked to different bib
16997             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
16998             EXECUTE remove_query;
16999             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17000             EXECUTE add_query;
17001         END IF;
17002
17003         RETURN NEW;
17004
17005     END IF;
17006
17007     IF TG_TABLE_NAME IN ('record_entry') THEN
17008         RETURN NEW; -- don't have 'opac_visible'
17009     END IF;
17010
17011     -- actor.org_unit, asset.copy_location, asset.copy_status
17012     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
17013
17014         RETURN NEW;
17015
17016     ELSIF NEW.opac_visible THEN -- add rows
17017
17018         IF TG_TABLE_NAME = 'org_unit' THEN
17019             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
17020         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17021             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
17022         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17023             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
17024         END IF;
17025  
17026         EXECUTE add_query;
17027  
17028     ELSE -- delete rows
17029
17030         IF TG_TABLE_NAME = 'org_unit' THEN
17031             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17032         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17033             remove_query := remove_query || 'location = ' || NEW.id || ');';
17034         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17035             remove_query := remove_query || 'status = ' || NEW.id || ');';
17036         END IF;
17037  
17038         EXECUTE remove_query;
17039  
17040     END IF;
17041  
17042     RETURN NEW;
17043 END;
17044 $func$ LANGUAGE PLPGSQL;
17045 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17046 Trigger function to update the copy OPAC visiblity cache.
17047 $$;
17048 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();
17049 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17050 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();
17051 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();
17052 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17053 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();
17054 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();
17055
17056 -- must create this rule explicitly; it is not inherited from asset.copy
17057 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;
17058
17059 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);
17060
17061 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17062 DECLARE
17063     moved_objects INT := 0;
17064     bib_id        INT := 0;
17065     bib_rec       biblio.record_entry%ROWTYPE;
17066     auth_link     authority.bib_linking%ROWTYPE;
17067 BEGIN
17068
17069     -- 1. Make source_record MARC a copy of the target_record to get auto-sync in linked bib records
17070     UPDATE authority.record_entry
17071       SET marc = (
17072         SELECT marc
17073           FROM authority.record_entry
17074           WHERE id = target_record
17075       )
17076       WHERE id = source_record;
17077
17078     -- 2. Update all bib records with the ID from target_record in their $0
17079     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre 
17080       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17081       WHERE abl.authority = target_record LOOP
17082
17083         UPDATE biblio.record_entry
17084           SET marc = REGEXP_REPLACE(marc, 
17085             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17086             E'\\1' || target_record || '<', 'g')
17087           WHERE id = bib_rec.id;
17088
17089           moved_objects := moved_objects + 1;
17090     END LOOP;
17091
17092     -- 3. "Delete" source_record
17093     DELETE FROM authority.record_entry
17094       WHERE id = source_record;
17095
17096     RETURN moved_objects;
17097 END;
17098 $func$ LANGUAGE plpgsql;
17099
17100 -- serial.record_entry already had an owner column spelled "owning_lib"
17101 -- Adjust the table and affected functions accordingly
17102
17103 ALTER TABLE serial.record_entry DROP COLUMN owner;
17104
17105 CREATE TABLE actor.usr_saved_search (
17106     id              SERIAL          PRIMARY KEY,
17107         owner           INT             NOT NULL REFERENCES actor.usr (id)
17108                                         ON DELETE CASCADE
17109                                         DEFERRABLE INITIALLY DEFERRED,
17110         name            TEXT            NOT NULL,
17111         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17112         query_text      TEXT            NOT NULL,
17113         query_type      TEXT            NOT NULL
17114                                         CONSTRAINT valid_query_text CHECK (
17115                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17116                                         -- we may add other types someday
17117         target          TEXT            NOT NULL
17118                                         CONSTRAINT valid_target CHECK (
17119                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17120         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17121 );
17122
17123 -- Apply Dan Wells' changes to the serial schema, from the
17124 -- seials-integration branch
17125
17126 CREATE TABLE serial.subscription_note (
17127         id           SERIAL PRIMARY KEY,
17128         subscription INT    NOT NULL
17129                             REFERENCES serial.subscription (id)
17130                             ON DELETE CASCADE
17131                             DEFERRABLE INITIALLY DEFERRED,
17132         creator      INT    NOT NULL
17133                             REFERENCES actor.usr (id)
17134                             DEFERRABLE INITIALLY DEFERRED,
17135         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17136         pub          BOOL   NOT NULL DEFAULT FALSE,
17137         title        TEXT   NOT NULL,
17138         value        TEXT   NOT NULL
17139 );
17140 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17141
17142 CREATE TABLE serial.distribution_note (
17143         id           SERIAL PRIMARY KEY,
17144         distribution INT    NOT NULL
17145                             REFERENCES serial.distribution (id)
17146                             ON DELETE CASCADE
17147                             DEFERRABLE INITIALLY DEFERRED,
17148         creator      INT    NOT NULL
17149                             REFERENCES actor.usr (id)
17150                             DEFERRABLE INITIALLY DEFERRED,
17151         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17152         pub          BOOL   NOT NULL DEFAULT FALSE,
17153         title        TEXT   NOT NULL,
17154         value        TEXT   NOT NULL
17155 );
17156 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17157
17158 ------- Begin surgery on serial.unit
17159
17160 ALTER TABLE serial.unit
17161         DROP COLUMN label;
17162
17163 ALTER TABLE serial.unit
17164         RENAME COLUMN label_sort_key TO sort_key;
17165
17166 ALTER TABLE serial.unit
17167         RENAME COLUMN contents TO detailed_contents;
17168
17169 ALTER TABLE serial.unit
17170         ADD COLUMN summary_contents TEXT;
17171
17172 UPDATE serial.unit
17173 SET summary_contents = detailed_contents;
17174
17175 ALTER TABLE serial.unit
17176         ALTER column summary_contents SET NOT NULL;
17177
17178 ------- End surgery on serial.unit
17179
17180 -- 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' );
17181
17182 -- Now rebuild the constraints dropped via cascade.
17183 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17184 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17185 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17186
17187 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17188
17189 DELETE FROM config.metabib_field_index_norm_map
17190     WHERE norm IN (
17191         SELECT id 
17192             FROM config.index_normalizer
17193             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17194     )
17195     AND field = 18
17196 ;
17197
17198 -- We won't necessarily use all of these, but they are here for completeness.
17199 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17200 -- Values are the EDI code value + 1200
17201
17202 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17203 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17204 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17205 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17206 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17207 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17208 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17209 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17210 (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.'),
17211 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17212 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17213 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17214 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17215 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17216 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17217 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17218 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17219 (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.'),
17220 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17221 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17222 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17223 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17224 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17225 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17226 (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.'),
17227 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17228 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17229 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17230 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17231 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17232 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17233 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17234 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17235 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17236 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17237 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17238 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17239 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17240 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17241 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17242 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17243 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17244 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17245 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17246 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17247 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17248 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17249 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17250 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17251 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17252 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17253 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17254 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17255 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17256 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17257 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17258 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17259 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17260 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17261 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17262 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17263 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17264 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17265 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17266 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17267 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17268 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17269 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17270 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17271 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17272 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17273 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17274 (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.'),
17275 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17276 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17277 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17278 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17279 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17280 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17281 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17282 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17283 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17284 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17285 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17286 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17287 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17288 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17289 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17290 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17291 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17292 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17293 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17294 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17295 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17296 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17297 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17298 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17299 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17300 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17301 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17302 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17303 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17304 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17305 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17306 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17307 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17308 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17309 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17310 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17311 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17312 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17313 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17314 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17315 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17316 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17317 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17318 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17319 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17320 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17321 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17322 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17323 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17324 (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.'),
17325 (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.'),
17326 (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.'),
17327 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17328 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17329 (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.'),
17330 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17331 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17332 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17333 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17334 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17335 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17336 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17337 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17338 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17339 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17340 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17341 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17342 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17343 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17344 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17345 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17346 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17347 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17348 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17349 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17350 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17351 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17352 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17353 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17354 (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.'),
17355 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17356 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17357 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17358 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17359 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17360 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17361 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17362 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17363 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17364 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17365 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17366 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17367 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17368 (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.'),
17369 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17370 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17371 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17372 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17373 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17374 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17375 (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.'),
17376 (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.'),
17377 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17378 (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.'),
17379 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17380 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17381 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17382 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17383 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17384 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17385 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17386 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17387 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17388 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17389 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17390 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17391 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17392 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17393 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17394 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17395 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17396 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17397 (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.'),
17398 (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.'),
17399 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17400 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17401 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17402 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17403 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17404 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17405 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17406 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17407 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17408 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17409 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17410 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17411 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17412 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17413 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17414 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17415 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17416 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17417 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17418 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17419 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17420 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17421 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17422 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17423 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17424 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17425 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17426 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17427 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17428 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17429 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17430 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17431 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17432 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17433 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17434 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17435 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17436 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17437 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17438 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17439 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17440 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17441 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17442 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17443 (1, 't', 1442, 'Number of months', 'The number of months.'),
17444 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17445 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17446 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17447 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17448 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17449 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17450 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17451 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17452 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17453 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17454 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17455 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17456 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17457 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17458 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17459 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17460 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17461 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17462 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17463 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17464 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17465 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17466 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17467 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17468 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17469 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17470 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17471 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17472 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17473 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17474 (1, 't', 1473, 'Agents', 'The number of agents.'),
17475 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17476 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17477 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17478 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17479 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17480 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17481 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17482 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17483 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17484 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17485 (1, 't', 1484, 'Departments', 'The number of departments.'),
17486 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17487 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17488 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17489 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17490 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17491 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17492 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17493 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17494 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17495 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17496 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17497 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17498 (1, 't', 1497, 'Executives', 'The number of executives.'),
17499 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17500 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17501 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17502 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17503 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17504 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17505 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17506 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17507 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17508 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17509 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17510 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17511 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17512 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17513 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17514 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17515 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17516 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17517 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17518 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17519 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17520 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17521 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17522 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17523 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17524 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17525 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17526 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17527 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17528 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17529 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17530 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17531 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17532 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17533 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17534 (1, 't', 1533, 'Seats',        'The number of seats.'),
17535 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17536 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17537 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17538 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17539 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17540 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17541 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17542 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17543 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17544 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17545 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17546 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17547 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17548 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17549 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17550 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17551 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17552 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17553 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17554 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17555 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17556 (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.'),
17557 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17558 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17559 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17560 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17561 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17562 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17563 (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.'),
17564 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17565 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17566 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17567 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17568 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17569 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17570 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17571 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17572 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17573 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17574 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17575 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17576 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17577 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17578 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17579 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17580 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17581 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17582 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17583 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17584 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17585 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17586 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17587 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17588 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17589 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17590 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17591 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17592 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17593 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17594 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17595 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17596 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17597 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17598 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17599 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17600 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17601 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17602 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17603 (1, 't', 1602, 'Patients',         'Number of patients.'),
17604 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17605 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17606 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17607 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17608 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17609 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17610 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17611 (1, 't', 1610, 'Operators',        'Number of operators.'),
17612 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17613 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17614 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17615 (1, 't', 1614, 'Machines',         'Number of machines.'),
17616 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17617 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17618 (1, 't', 1617, 'Directors',        'Number of directors.'),
17619 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17620 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17621 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17622 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17623 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17624 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17625 (1, 't', 1624, 'Beds', 'Number of beds.'),
17626 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17627 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17628 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17629 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
17630 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
17631 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
17632 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
17633 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
17634 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
17635 (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.'),
17636 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
17637 (1, 't', 1636, 'Professor', 'The number of professors.'),
17638 (1, 't', 1637, 'Seller',    'The number of sellers.'),
17639 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
17640 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
17641 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
17642 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
17643 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
17644 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
17645 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
17646 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
17647 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
17648 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
17649 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
17650 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
17651 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
17652 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
17653 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
17654 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
17655 (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.'),
17656 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
17657 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
17658 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
17659 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
17660 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
17661 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
17662 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
17663 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
17664 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
17665 (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.'),
17666 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
17667 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
17668 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
17669 (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.'),
17670 (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.'),
17671 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
17672 (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.'),
17673 (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.'),
17674 (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.'),
17675 (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.'),
17676 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
17677 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
17678 (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.'),
17679 (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.'),
17680 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
17681 (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.'),
17682 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
17683 (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.'),
17684 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
17685 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
17686 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
17687 (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).'),
17688 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
17689 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
17690 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
17691 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
17692 ;
17693 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
17694
17695 CREATE TABLE acq.serial_claim (
17696     id     SERIAL           PRIMARY KEY,
17697     type   INT              NOT NULL REFERENCES acq.claim_type
17698                                      DEFERRABLE INITIALLY DEFERRED,
17699     item    BIGINT          NOT NULL REFERENCES serial.item
17700                                      DEFERRABLE INITIALLY DEFERRED
17701 );
17702
17703 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
17704
17705 CREATE TABLE acq.serial_claim_event (
17706     id             BIGSERIAL        PRIMARY KEY,
17707     type           INT              NOT NULL REFERENCES acq.claim_event_type
17708                                              DEFERRABLE INITIALLY DEFERRED,
17709     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
17710                                              DEFERRABLE INITIALLY DEFERRED,
17711     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
17712     creator        INT              NOT NULL REFERENCES actor.usr
17713                                              DEFERRABLE INITIALLY DEFERRED,
17714     note           TEXT
17715 );
17716
17717 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
17718
17719 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
17720
17721 -- now what about the auditor.*_lifecycle views??
17722
17723 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17724     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
17725 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17726     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
17727 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
17728 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
17729
17730 CREATE TABLE asset.call_number_class (
17731     id             bigserial     PRIMARY KEY,
17732     name           TEXT          NOT NULL,
17733     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
17734     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17735 );
17736
17737 COMMENT ON TABLE asset.call_number_class IS $$
17738 Defines the call number normalization database functions in the "normalizer"
17739 column and the tag/subfield combinations to use to lookup the call number in
17740 the "field" column for a given classification scheme. Tag/subfield combinations
17741 are delimited by commas.
17742 $$;
17743
17744 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
17745     ('Generic', 'asset.label_normalizer_generic'),
17746     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
17747     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
17748 ;
17749
17750 -- Generic fields
17751 UPDATE asset.call_number_class
17752     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17753     WHERE id = 1
17754 ;
17755
17756 -- Dewey fields
17757 UPDATE asset.call_number_class
17758     SET field = '080ab,082ab'
17759     WHERE id = 2
17760 ;
17761
17762 -- LC fields
17763 UPDATE asset.call_number_class
17764     SET field = '050ab,055ab'
17765     WHERE id = 3
17766 ;
17767  
17768 ALTER TABLE asset.call_number
17769         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
17770                 REFERENCES asset.call_number_class(id)
17771                 DEFERRABLE INITIALLY DEFERRED;
17772
17773 ALTER TABLE asset.call_number
17774         ADD COLUMN label_sortkey TEXT;
17775
17776 CREATE INDEX asset_call_number_label_sortkey
17777         ON asset.call_number(oils_text_as_bytea(label_sortkey));
17778
17779 ALTER TABLE auditor.asset_call_number_history
17780         ADD COLUMN label_class BIGINT;
17781
17782 ALTER TABLE auditor.asset_call_number_history
17783         ADD COLUMN label_sortkey TEXT;
17784
17785 -- Pick up the new columns in dependent views
17786
17787 DROP VIEW auditor.asset_call_number_lifecycle;
17788
17789 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17790
17791 DROP VIEW auditor.asset_call_number_lifecycle;
17792
17793 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17794
17795 DROP VIEW IF EXISTS stats.fleshed_call_number;
17796
17797 CREATE VIEW stats.fleshed_call_number AS
17798         SELECT  cn.*,
17799             CAST(cn.create_date AS DATE) AS create_date_day,
17800         CAST(cn.edit_date AS DATE) AS edit_date_day,
17801         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
17802         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
17803             rd.item_lang,
17804                 rd.item_type,
17805                 rd.item_form
17806         FROM    asset.call_number cn
17807                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
17808
17809 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
17810 DECLARE
17811     sortkey        TEXT := '';
17812 BEGIN
17813     sortkey := NEW.label_sortkey;
17814
17815     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
17816        quote_literal( NEW.label ) || ')'
17817        FROM asset.call_number_class acnc
17818        WHERE acnc.id = NEW.label_class
17819        INTO sortkey;
17820
17821     NEW.label_sortkey = sortkey;
17822
17823     RETURN NEW;
17824 END;
17825 $func$ LANGUAGE PLPGSQL;
17826
17827 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
17828     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
17829     # thus could probably be considered a derived work, although nothing was
17830     # directly copied - but to err on the safe side of providing attribution:
17831     # Copyright (C) 2007 LibLime
17832     # Licensed under the GPL v2 or later
17833
17834     use strict;
17835     use warnings;
17836
17837     # Converts the callnumber to uppercase
17838     # Strips spaces from start and end of the call number
17839     # Converts anything other than letters, digits, and periods into underscores
17840     # Collapses multiple underscores into a single underscore
17841     my $callnum = uc(shift);
17842     $callnum =~ s/^\s//g;
17843     $callnum =~ s/\s$//g;
17844     $callnum =~ s/[^A-Z0-9_.]/_/g;
17845     $callnum =~ s/_{2,}/_/g;
17846
17847     return $callnum;
17848 $func$ LANGUAGE PLPERLU;
17849
17850 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
17851     # Derived from the Koha C4::ClassSortRoutine::Dewey module
17852     # Copyright (C) 2007 LibLime
17853     # Licensed under the GPL v2 or later
17854
17855     use strict;
17856     use warnings;
17857
17858     my $init = uc(shift);
17859     $init =~ s/^\s+//;
17860     $init =~ s/\s+$//;
17861     $init =~ s!/!!g;
17862     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
17863     my @tokens = split /\.|\s+/, $init;
17864     my $digit_group_count = 0;
17865     for (my $i = 0; $i <= $#tokens; $i++) {
17866         if ($tokens[$i] =~ /^\d+$/) {
17867             $digit_group_count++;
17868             if (2 == $digit_group_count) {
17869                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
17870                 $tokens[$i] =~ tr/ /0/;
17871             }
17872         }
17873     }
17874     my $key = join("_", @tokens);
17875     $key =~ s/[^\p{IsAlnum}_]//g;
17876
17877     return $key;
17878
17879 $func$ LANGUAGE PLPERLU;
17880
17881 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
17882     use strict;
17883     use warnings;
17884
17885     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
17886     # The author hopes to upload it to CPAN some day, which would make our lives easier
17887     use Library::CallNumber::LC;
17888
17889     my $callnum = Library::CallNumber::LC->new(shift);
17890     return $callnum->normalize();
17891
17892 $func$ LANGUAGE PLPERLU;
17893
17894 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$
17895 DECLARE
17896     ans RECORD;
17897     trans INT;
17898 BEGIN
17899     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;
17900
17901     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
17902         RETURN QUERY
17903         SELECT  ans.depth,
17904                 ans.id,
17905                 COUNT( av.id ),
17906                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
17907                 COUNT( av.id ),
17908                 trans
17909           FROM
17910                 actor.org_unit_descendants(ans.id) d
17911                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
17912                 JOIN asset.copy cp ON (cp.id = av.id)
17913           GROUP BY 1,2,6;
17914
17915         IF NOT FOUND THEN
17916             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
17917         END IF;
17918
17919     END LOOP;
17920
17921     RETURN;
17922 END;
17923 $f$ LANGUAGE PLPGSQL;
17924
17925 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$
17926 DECLARE
17927     ans RECORD;
17928     trans INT;
17929 BEGIN
17930     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;
17931
17932     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
17933         RETURN QUERY
17934         SELECT  -1,
17935                 ans.id,
17936                 COUNT( av.id ),
17937                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
17938                 COUNT( av.id ),
17939                 trans
17940           FROM
17941                 actor.org_unit_descendants(ans.id) d
17942                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
17943                 JOIN asset.copy cp ON (cp.id = av.id)
17944           GROUP BY 1,2,6;
17945
17946         IF NOT FOUND THEN
17947             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
17948         END IF;
17949
17950     END LOOP;
17951
17952     RETURN;
17953 END;
17954 $f$ LANGUAGE PLPGSQL;
17955
17956 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$
17957 DECLARE
17958     ans RECORD;
17959     trans INT;
17960 BEGIN
17961     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;
17962
17963     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
17964         RETURN QUERY
17965         SELECT  ans.depth,
17966                 ans.id,
17967                 COUNT( cp.id ),
17968                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
17969                 COUNT( cp.id ),
17970                 trans
17971           FROM
17972                 actor.org_unit_descendants(ans.id) d
17973                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
17974                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
17975           GROUP BY 1,2,6;
17976
17977         IF NOT FOUND THEN
17978             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
17979         END IF;
17980
17981     END LOOP;
17982
17983     RETURN;
17984 END;
17985 $f$ LANGUAGE PLPGSQL;
17986
17987 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$
17988 DECLARE
17989     ans RECORD;
17990     trans INT;
17991 BEGIN
17992     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;
17993
17994     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
17995         RETURN QUERY
17996         SELECT  -1,
17997                 ans.id,
17998                 COUNT( cp.id ),
17999                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18000                 COUNT( cp.id ),
18001                 trans
18002           FROM
18003                 actor.org_unit_descendants(ans.id) d
18004                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18005                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18006           GROUP BY 1,2,6;
18007
18008         IF NOT FOUND THEN
18009             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18010         END IF;
18011
18012     END LOOP;
18013
18014     RETURN;
18015 END;
18016 $f$ LANGUAGE PLPGSQL;
18017
18018 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$
18019 BEGIN
18020     IF staff IS TRUE THEN
18021         IF place > 0 THEN
18022             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
18023         ELSE
18024             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
18025         END IF;
18026     ELSE
18027         IF place > 0 THEN
18028             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18029         ELSE
18030             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18031         END IF;
18032     END IF;
18033
18034     RETURN;
18035 END;
18036 $f$ LANGUAGE PLPGSQL;
18037
18038 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$
18039 DECLARE
18040     ans RECORD;
18041     trans INT;
18042 BEGIN
18043     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;
18044
18045     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
18046         RETURN QUERY
18047         SELECT  ans.depth,
18048                 ans.id,
18049                 COUNT( av.id ),
18050                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18051                 COUNT( av.id ),
18052                 trans
18053           FROM
18054                 actor.org_unit_descendants(ans.id) d
18055                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18056                 JOIN asset.copy cp ON (cp.id = av.id)
18057                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18058           GROUP BY 1,2,6;
18059
18060         IF NOT FOUND THEN
18061             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18062         END IF;
18063
18064     END LOOP;
18065
18066     RETURN;
18067 END;
18068 $f$ LANGUAGE PLPGSQL;
18069
18070 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$
18071 DECLARE
18072     ans RECORD;
18073     trans INT;
18074 BEGIN
18075     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;
18076
18077     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18078         RETURN QUERY
18079         SELECT  -1,
18080                 ans.id,
18081                 COUNT( av.id ),
18082                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18083                 COUNT( av.id ),
18084                 trans
18085           FROM
18086                 actor.org_unit_descendants(ans.id) d
18087                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18088                 JOIN asset.copy cp ON (cp.id = av.id)
18089                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18090           GROUP BY 1,2,6;
18091
18092         IF NOT FOUND THEN
18093             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18094         END IF;
18095
18096     END LOOP;
18097
18098     RETURN;
18099 END;
18100 $f$ LANGUAGE PLPGSQL;
18101
18102 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$
18103 DECLARE
18104     ans RECORD;
18105     trans INT;
18106 BEGIN
18107     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;
18108
18109     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
18110         RETURN QUERY
18111         SELECT  ans.depth,
18112                 ans.id,
18113                 COUNT( cp.id ),
18114                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18115                 COUNT( cp.id ),
18116                 trans
18117           FROM
18118                 actor.org_unit_descendants(ans.id) d
18119                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18120                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18121                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18122           GROUP BY 1,2,6;
18123
18124         IF NOT FOUND THEN
18125             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18126         END IF;
18127
18128     END LOOP;
18129
18130     RETURN;
18131 END;
18132 $f$ LANGUAGE PLPGSQL;
18133
18134 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$
18135 DECLARE
18136     ans RECORD;
18137     trans INT;
18138 BEGIN
18139     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;
18140
18141     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18142         RETURN QUERY
18143         SELECT  -1,
18144                 ans.id,
18145                 COUNT( cp.id ),
18146                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18147                 COUNT( cp.id ),
18148                 trans
18149           FROM
18150                 actor.org_unit_descendants(ans.id) d
18151                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18152                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18153                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18154           GROUP BY 1,2,6;
18155
18156         IF NOT FOUND THEN
18157             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18158         END IF;
18159
18160     END LOOP;
18161
18162     RETURN;
18163 END;
18164 $f$ LANGUAGE PLPGSQL;
18165
18166 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$
18167 BEGIN
18168     IF staff IS TRUE THEN
18169         IF place > 0 THEN
18170             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18171         ELSE
18172             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18173         END IF;
18174     ELSE
18175         IF place > 0 THEN
18176             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18177         ELSE
18178             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18179         END IF;
18180     END IF;
18181
18182     RETURN;
18183 END;
18184 $f$ LANGUAGE PLPGSQL;
18185
18186 -- No transaction is required
18187
18188 -- Triggers on the vandelay.queued_*_record tables delete entries from
18189 -- the associated vandelay.queued_*_record_attr tables based on the record's
18190 -- ID; create an index on that column to avoid sequential scans for each
18191 -- queued record that is deleted
18192 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18193 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18194
18195 -- Avoid sequential scans for queue retrieval operations by providing an
18196 -- index on the queue column
18197 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18198 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18199
18200 -- Start picking up call number label prefixes and suffixes
18201 -- from asset.copy_location
18202 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18203 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18204
18205 DROP VIEW auditor.asset_copy_lifecycle;
18206
18207 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18208
18209 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18210
18211 -- Let's not break existing reports
18212 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18213 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18214
18215 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18216 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18217 SELECT  r.id,
18218     r.fingerprint,
18219     r.quality,
18220     r.tcn_source,
18221     r.tcn_value,
18222     FIRST(title.value) AS title,
18223     FIRST(author.value) AS author,
18224     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18225     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18226     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18227     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18228   FROM  biblio.record_entry r
18229     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18230     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18231     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18232     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18233     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18234     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18235   GROUP BY 1,2,3,4,5;
18236
18237 -- Correct the ISSN array definition for reporter.simple_record
18238
18239 CREATE OR REPLACE VIEW reporter.simple_record AS
18240 SELECT  r.id,
18241         s.metarecord,
18242         r.fingerprint,
18243         r.quality,
18244         r.tcn_source,
18245         r.tcn_value,
18246         title.value AS title,
18247         uniform_title.value AS uniform_title,
18248         author.value AS author,
18249         publisher.value AS publisher,
18250         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18251         series_title.value AS series_title,
18252         series_statement.value AS series_statement,
18253         summary.value AS summary,
18254         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18255         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18256         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18257         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18258         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18259         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18260         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18261         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
18262   FROM  biblio.record_entry r
18263         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18264         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18265         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18266         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18267         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18268         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18269         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18270         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18271         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')
18272         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18273         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18274   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18275
18276 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18277     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
18278 $$ LANGUAGE SQL;
18279
18280 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18281 BEGIN
18282     IF TG_OP = 'DELETE' THEN
18283         PERFORM reporter.simple_rec_delete(NEW.id);
18284     ELSE
18285         PERFORM reporter.simple_rec_update(NEW.id);
18286     END IF;
18287
18288     RETURN NEW;
18289 END;
18290 $func$ LANGUAGE PLPGSQL;
18291
18292 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
18293
18294 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18295
18296 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18297
18298 UPDATE config.org_unit_setting_type
18299     SET view_perm = (SELECT id FROM permission.perm_list
18300         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18301     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18302
18303 UPDATE config.org_unit_setting_type
18304     SET update_perm = (SELECT id FROM permission.perm_list
18305         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18306     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18307
18308 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18309     VALUES (
18310         'opac.fully_compressed_serial_holdings',
18311         'OPAC: Use fully compressed serial holdings',
18312         'Show fully compressed serial holdings for all libraries at and below
18313         the current context unit',
18314         'bool'
18315     );
18316
18317 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18318     use strict;
18319     use warnings;
18320
18321     use utf8;
18322     use MARC::Record;
18323     use MARC::File::XML (BinaryEncoding => 'UTF8');
18324     use UUID::Tiny ':std';
18325
18326     my $xml = shift() or return undef;
18327
18328     my $r;
18329
18330     # Prevent errors in XML parsing from blowing out ungracefully
18331     eval {
18332         $r = MARC::Record->new_from_xml( $xml );
18333         1;
18334     } or do {
18335        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18336     };
18337
18338     if (!$r) {
18339        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18340     }
18341
18342     # From http://www.loc.gov/standards/sourcelist/subject.html
18343     my $thes_code_map = {
18344         a => 'lcsh',
18345         b => 'lcshac',
18346         c => 'mesh',
18347         d => 'nal',
18348         k => 'cash',
18349         n => 'notapplicable',
18350         r => 'aat',
18351         s => 'sears',
18352         v => 'rvm',
18353     };
18354
18355     # Default to "No attempt to code" if the leader is horribly broken
18356     my $fixed_field = $r->field('008');
18357     my $thes_char = '|';
18358     if ($fixed_field) {
18359         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18360     }
18361
18362     my $thes_code = 'UNDEFINED';
18363
18364     if ($thes_char eq 'z') {
18365         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18366         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18367     } elsif ($thes_code_map->{$thes_char}) {
18368         $thes_code = $thes_code_map->{$thes_char};
18369     }
18370
18371     my $auth_txt = '';
18372     my $head = $r->field('1..');
18373     if ($head) {
18374         # Concatenate all of these subfields together, prefixed by their code
18375         # to prevent collisions along the lines of "Fiction, North Carolina"
18376         foreach my $sf ($head->subfields()) {
18377             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18378         }
18379     }
18380
18381     # Perhaps better to parameterize the spi and pass as a parameter
18382     $auth_txt =~ s/'//go;
18383
18384     if ($auth_txt) {
18385         my $result = spi_exec_query("SELECT public.naco_normalize('$auth_txt') AS norm_text");
18386         my $norm_txt = $result->{rows}[0]->{norm_text};
18387         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18388     }
18389
18390     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18391 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18392
18393 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18394 /**
18395 * Extract the authority heading, thesaurus, and NACO-normalized values
18396 * from an authority record. The primary purpose is to build a unique
18397 * index to defend against duplicated authority records from the same
18398 * thesaurus.
18399 */
18400 $$;
18401
18402 DROP INDEX authority.authority_record_unique_tcn;
18403 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18404 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18405
18406 ALTER TABLE acq.provider_contact
18407         ALTER COLUMN name SET NOT NULL;
18408
18409 ALTER TABLE actor.stat_cat
18410         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18411
18412 -- Recreate some foreign keys that were somehow dropped, probably
18413 -- by some kind of cascade from an inherited table:
18414
18415 ALTER TABLE action.reservation_transit_copy
18416         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
18417                 REFERENCES booking.resource(id)
18418                 ON DELETE CASCADE
18419                 DEFERRABLE INITIALLY DEFERRED,
18420         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
18421                 REFERENCES booking.reservation(id)
18422                 ON DELETE SET NULL
18423                 DEFERRABLE INITIALLY DEFERRED;
18424
18425 CREATE INDEX user_bucket_item_target_user_idx
18426         ON container.user_bucket_item ( target_user );
18427
18428 CREATE INDEX m_c_t_collector_idx
18429         ON money.collections_tracker ( collector );
18430
18431 CREATE INDEX aud_actor_usr_address_hist_id_idx
18432         ON auditor.actor_usr_address_history ( id );
18433
18434 CREATE INDEX aud_actor_usr_hist_id_idx
18435         ON auditor.actor_usr_history ( id );
18436
18437 CREATE INDEX aud_asset_cn_hist_creator_idx
18438         ON auditor.asset_call_number_history ( creator );
18439
18440 CREATE INDEX aud_asset_cn_hist_editor_idx
18441         ON auditor.asset_call_number_history ( editor );
18442
18443 CREATE INDEX aud_asset_cp_hist_creator_idx
18444         ON auditor.asset_copy_history ( creator );
18445
18446 CREATE INDEX aud_asset_cp_hist_editor_idx
18447         ON auditor.asset_copy_history ( editor );
18448
18449 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18450         ON auditor.biblio_record_entry_history ( creator );
18451
18452 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18453         ON auditor.biblio_record_entry_history ( editor );
18454
18455 CREATE TABLE action.hold_request_note (
18456
18457     id     BIGSERIAL PRIMARY KEY,
18458     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18459                               ON DELETE CASCADE
18460                               DEFERRABLE INITIALLY DEFERRED,
18461     title  TEXT      NOT NULL,
18462     body   TEXT      NOT NULL,
18463     slip   BOOL      NOT NULL DEFAULT FALSE,
18464     pub    BOOL      NOT NULL DEFAULT FALSE,
18465     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18466
18467 );
18468 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18469
18470 -- Tweak a constraint to add a CASCADE
18471
18472 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18473
18474 ALTER TABLE action.hold_notification
18475         ADD CONSTRAINT hold_notification_hold_fkey
18476                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18477                 ON DELETE CASCADE
18478                 DEFERRABLE INITIALLY DEFERRED;
18479
18480 CREATE TRIGGER asset_label_sortkey_trigger
18481     BEFORE UPDATE OR INSERT ON asset.call_number
18482     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18483
18484 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18485 RETURNS VOID AS $$
18486 --
18487 -- Delete expired circulation bucket items for all users that have
18488 -- a setting for patron.max_reading_list_interval.
18489 --
18490 DECLARE
18491     today        TIMESTAMP WITH TIME ZONE;
18492     threshold    TIMESTAMP WITH TIME ZONE;
18493         usr_setting  RECORD;
18494 BEGIN
18495         SELECT date_trunc( 'day', now() ) INTO today;
18496         --
18497         FOR usr_setting in
18498                 SELECT
18499                         usr,
18500                         value
18501                 FROM
18502                         actor.usr_setting
18503                 WHERE
18504                         name = 'patron.max_reading_list_interval'
18505         LOOP
18506                 --
18507                 -- Make sure the setting is a valid interval
18508                 --
18509                 BEGIN
18510                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18511                 EXCEPTION
18512                         WHEN OTHERS THEN
18513                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18514                                         usr_setting.usr, usr_setting.value;
18515                                 CONTINUE;
18516                 END;
18517                 --
18518                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18519                 --
18520         DELETE FROM container.copy_bucket_item
18521         WHERE
18522                 bucket IN
18523                 (
18524                     SELECT
18525                         id
18526                     FROM
18527                         container.copy_bucket
18528                     WHERE
18529                         owner = usr_setting.usr
18530                         AND btype = 'circ_history'
18531                 )
18532                 AND create_time < threshold;
18533         END LOOP;
18534         --
18535 END;
18536 $$ LANGUAGE plpgsql;
18537
18538 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18539 /*
18540  * Delete expired circulation bucket items for all users that have
18541  * a setting for patron.max_reading_list_interval.
18542 */
18543 $$;
18544
18545 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18546          ac_usr IN INTEGER
18547 ) RETURNS VOID AS $$
18548 --
18549 -- Delete old circulation bucket items for a specified user.
18550 -- "Old" means older than the interval specified by a
18551 -- user-level setting, if it is so specified.
18552 --
18553 DECLARE
18554     threshold TIMESTAMP WITH TIME ZONE;
18555 BEGIN
18556         -- Sanity check
18557         IF ac_usr IS NULL THEN
18558                 RETURN;
18559         END IF;
18560         -- Determine the threshold date that defines "old".  Subtract the
18561         -- interval from the system date, then truncate to midnight.
18562         SELECT
18563                 date_trunc( 
18564                         'day',
18565                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18566                 )
18567         INTO
18568                 threshold
18569         FROM
18570                 actor.usr_setting
18571         WHERE
18572                 usr = ac_usr
18573                 AND name = 'patron.max_reading_list_interval';
18574         --
18575         IF threshold is null THEN
18576                 -- No interval defined; don't delete anything
18577                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18578                 return;
18579         END IF;
18580         --
18581         -- RAISE NOTICE 'Date threshold: %', threshold;
18582         --
18583         -- Threshold found; do the delete
18584         delete from container.copy_bucket_item
18585         where
18586                 bucket in
18587                 (
18588                         select
18589                                 id
18590                         from
18591                                 container.copy_bucket
18592                         where
18593                                 owner = ac_usr
18594                                 and btype = 'circ_history'
18595                 )
18596                 and create_time < threshold;
18597         --
18598         RETURN;
18599 END;
18600 $$ LANGUAGE plpgsql;
18601
18602 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18603 /*
18604  * Delete old circulation bucket items for a specified user.
18605  * "Old" means older than the interval specified by a
18606  * user-level setting, if it is so specified.
18607 */
18608 $$;
18609
18610 CREATE OR REPLACE VIEW reporter.hold_request_record AS
18611 SELECT  id,
18612     target,
18613     hold_type,
18614     CASE
18615         WHEN hold_type = 'T'
18616             THEN target
18617         WHEN hold_type = 'I'
18618             THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
18619         WHEN hold_type = 'V'
18620             THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
18621         WHEN hold_type IN ('C','R','F')
18622             THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
18623         WHEN hold_type = 'M'
18624             THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
18625     END AS bib_record
18626   FROM  action.hold_request ahr;
18627
18628 UPDATE  metabib.rec_descriptor
18629   SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
18630         date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
18631
18632 -- Change some ints to bigints:
18633
18634 ALTER TABLE container.biblio_record_entry_bucket_item
18635         ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
18636
18637 ALTER TABLE vandelay.queued_bib_record
18638         ALTER COLUMN imported_as SET DATA TYPE bigint;
18639
18640 ALTER TABLE action.hold_copy_map
18641         ALTER COLUMN id SET DATA TYPE bigint;
18642
18643 -- Make due times get pushed to 23:59:59 on insert OR update
18644 DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
18645 CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
18646
18647 COMMIT;
18648
18649 -- Some operations go outside of the transaction, because they may
18650 -- legitimately fail.
18651
18652 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
18653 \qecho doesn't exist; ignore those errors if they occur.
18654
18655 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
18656
18657 ALTER TABLE auditor.action_hold_request_history
18658 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
18659
18660 ALTER TABLE auditor.action_hold_request_history
18661 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
18662
18663 \qecho Outside of the transaction: adding indexes that may or may not exist.
18664 \qecho If any of these CREATE INDEX statements fails because the index already
18665 \qecho exists, ignore the failure.
18666
18667 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
18668 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
18669 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
18670 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
18671 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
18672 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
18673 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
18674 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
18675 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
18676 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
18677 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
18678 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
18679 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
18680 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
18681 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
18682 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
18683 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
18684 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
18685 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
18686 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
18687 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
18688 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
18689 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
18690 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
18691 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
18692 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
18693 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
18694 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
18695 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
18696 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
18697 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
18698 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
18699 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
18700
18701 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
18702
18703 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
18704
18705 \qecho If the following CREATE INDEX fails, It will be necessary to do some
18706 \qecho data cleanup as described in the comments.
18707
18708 CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
18709     ON authority.record_entry (authority.normalize_heading(marc))
18710         WHERE deleted IS FALSE or deleted = FALSE;
18711
18712 -- If the unique index fails, uncomment the following to create
18713 -- a regular index that will help find the duplicates in a hurry:
18714 --CREATE INDEX by_heading_and_thesaurus
18715 --    ON authority.record_entry (authority.normalize_heading(marc))
18716 --    WHERE deleted IS FALSE or deleted = FALSE
18717 --;
18718
18719 -- Then find the duplicates like so to get an idea of how much
18720 -- pain you're looking at to clean things up:
18721 --SELECT id, authority.normalize_heading(marc)
18722 --    FROM authority.record_entry
18723 --    WHERE authority.normalize_heading(marc) IN (
18724 --        SELECT authority.normalize_heading(marc)
18725 --        FROM authority.record_entry
18726 --        GROUP BY authority.normalize_heading(marc)
18727 --        HAVING COUNT(*) > 1
18728 --    )
18729 --;
18730
18731 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
18732 -- statement succeeds, drop the temporary index to avoid unnecessary
18733 -- duplication:
18734 -- DROP INDEX authority.by_heading_and_thesaurus;
18735
18736 \qecho Upgrade script completed.