]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
1.6.1-2.0 upgrade script needs to fall in line with reality, re 0442
[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 -- Hard due-date functionality
6191 CREATE TABLE config.hard_due_date (
6192         id          SERIAL      PRIMARY KEY,
6193         name        TEXT        NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
6194         ceiling_date    TIMESTAMPTZ NOT NULL,
6195         forceto     BOOL        NOT NULL,
6196         owner       INT         NOT NULL
6197 );
6198
6199 CREATE TABLE config.hard_due_date_values (
6200     id                  SERIAL      PRIMARY KEY,
6201     hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
6202                                     DEFERRABLE INITIALLY DEFERRED,
6203     ceiling_date        TIMESTAMPTZ NOT NULL,
6204     active_date         TIMESTAMPTZ NOT NULL
6205 );
6206
6207 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN hard_due_date INT REFERENCES config.hard_due_date (id);
6208 ALTER TABLE config.rule_circ_duration DROP COLUMN date_ceiling;
6209
6210 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
6211 DECLARE
6212     temp_value  config.hard_due_date_values%ROWTYPE;
6213     updated     INT := 0;
6214 BEGIN
6215     FOR temp_value IN
6216       SELECT  DISTINCT ON (hard_due_date) *
6217         FROM  config.hard_due_date_values
6218         WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
6219         ORDER BY active_date DESC -- Latest (nearest to us) active time
6220    LOOP
6221         UPDATE  config.hard_due_date
6222           SET   ceiling_date = temp_value.ceiling_date
6223           WHERE id = temp_value.hard_due_date
6224                 AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd
6225
6226         IF FOUND THEN
6227             updated := updated + 1;
6228         END IF;
6229     END LOOP;
6230
6231     RETURN updated;
6232 END;
6233 $func$ LANGUAGE plpgsql;
6234
6235 -- Correct some long-standing misspellings involving variations of "recur"
6236
6237 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
6238 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6239
6240 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
6241 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6242
6243 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
6244 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
6245
6246 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
6247
6248 -- Might as well keep the comment in sync as well
6249 COMMENT ON TABLE config.rule_recurring_fine IS $$
6250 /*
6251  * Copyright (C) 2005  Georgia Public Library Service 
6252  * Mike Rylander <mrylander@gmail.com>
6253  *
6254  * Circulation Recurring Fine rules
6255  *
6256  * Each circulation is given a recurring fine amount based on one of
6257  * these rules.  The recurrence_interval should not be any shorter
6258  * than the interval between runs of the fine_processor.pl script
6259  * (which is run from CRON), or you could miss fines.
6260  * 
6261  *
6262  * ****
6263  *
6264  * This program is free software; you can redistribute it and/or
6265  * modify it under the terms of the GNU General Public License
6266  * as published by the Free Software Foundation; either version 2
6267  * of the License, or (at your option) any later version.
6268  *
6269  * This program is distributed in the hope that it will be useful,
6270  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6271  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6272  * GNU General Public License for more details.
6273  */
6274 $$;
6275
6276 -- Extend the name change to some related views:
6277
6278 DROP VIEW IF EXISTS reporter.overdue_circs;
6279
6280 CREATE OR REPLACE VIEW reporter.overdue_circs AS
6281 SELECT  *
6282   FROM  action.circulation
6283     WHERE checkin_time is null
6284                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
6285                                 AND due_date < now();
6286
6287 DROP VIEW IF EXISTS stats.fleshed_circulation;
6288
6289 DROP VIEW IF EXISTS stats.fleshed_copy;
6290
6291 CREATE VIEW stats.fleshed_copy AS
6292         SELECT  cp.*,
6293         CAST(cp.create_date AS DATE) AS create_date_day,
6294         CAST(cp.edit_date AS DATE) AS edit_date_day,
6295         DATE_TRUNC('hour', cp.create_date) AS create_date_hour,
6296         DATE_TRUNC('hour', cp.edit_date) AS edit_date_hour,
6297                 cn.label AS call_number_label,
6298                 cn.owning_lib,
6299                 rd.item_lang,
6300                 rd.item_type,
6301                 rd.item_form
6302         FROM    asset.copy cp
6303                 JOIN asset.call_number cn ON (cp.call_number = cn.id)
6304                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
6305
6306 CREATE VIEW stats.fleshed_circulation AS
6307         SELECT  c.*,
6308                 CAST(c.xact_start AS DATE) AS start_date_day,
6309                 CAST(c.xact_finish AS DATE) AS finish_date_day,
6310                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
6311                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
6312                 cp.call_number_label,
6313                 cp.owning_lib,
6314                 cp.item_lang,
6315                 cp.item_type,
6316                 cp.item_form
6317         FROM    action.circulation c
6318                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
6319
6320 -- Drop a view temporarily in order to alter action.all_circulation, upon
6321 -- which it is dependent.  We will recreate the view later.
6322
6323 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
6324
6325 -- You would think that CREATE OR REPLACE would be enough, but in testing
6326 -- PostgreSQL complained about renaming the columns in the view. So we
6327 -- drop the view first.
6328 DROP VIEW IF EXISTS action.all_circulation;
6329
6330 CREATE OR REPLACE VIEW action.all_circulation AS
6331     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6332         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6333         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6334         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6335         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6336         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6337       FROM  action.aged_circulation
6338             UNION ALL
6339     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,
6340         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,
6341         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6342         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6343         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6344         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6345         circ.parent_circ
6346       FROM  action.circulation circ
6347         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6348         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6349         JOIN actor.usr p ON (circ.usr = p.id)
6350         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6351         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
6352
6353 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
6354
6355 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
6356  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
6357    FROM asset."copy" cp
6358    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
6359    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
6360    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
6361   GROUP BY cp.id;
6362
6363 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
6364
6365 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
6366
6367 -- Rebuild dependent views
6368
6369 DROP VIEW IF EXISTS action.billable_circulations;
6370
6371 CREATE OR REPLACE VIEW action.billable_circulations AS
6372     SELECT  *
6373       FROM  action.circulation
6374       WHERE xact_finish IS NULL;
6375
6376 DROP VIEW IF EXISTS action.open_circulation;
6377
6378 CREATE OR REPLACE VIEW action.open_circulation AS
6379     SELECT  *
6380       FROM  action.circulation
6381       WHERE checkin_time IS NULL
6382       ORDER BY due_date;
6383
6384 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
6385 DECLARE
6386 found char := 'N';
6387 BEGIN
6388
6389     -- If there are any renewals for this circulation, don't archive or delete
6390     -- it yet.   We'll do so later, when we archive and delete the renewals.
6391
6392     SELECT 'Y' INTO found
6393     FROM action.circulation
6394     WHERE parent_circ = OLD.id
6395     LIMIT 1;
6396
6397     IF found = 'Y' THEN
6398         RETURN NULL;  -- don't delete
6399         END IF;
6400
6401     -- Archive a copy of the old row to action.aged_circulation
6402
6403     INSERT INTO action.aged_circulation
6404         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6405         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6406         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6407         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6408         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6409         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
6410       SELECT
6411         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6412         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6413         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6414         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6415         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6416         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6417         FROM action.all_circulation WHERE id = OLD.id;
6418
6419     RETURN OLD;
6420 END;
6421 $$ LANGUAGE 'plpgsql';
6422
6423 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
6424
6425 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
6426
6427 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
6428
6429 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$
6430 DECLARE
6431     current_requestor_group    permission.grp_tree%ROWTYPE;
6432     requestor_object    actor.usr%ROWTYPE;
6433     user_object        actor.usr%ROWTYPE;
6434     item_object        asset.copy%ROWTYPE;
6435     item_cn_object        asset.call_number%ROWTYPE;
6436     rec_descriptor        metabib.rec_descriptor%ROWTYPE;
6437     current_mp_weight    FLOAT;
6438     matchpoint_weight    FLOAT;
6439     tmp_weight        FLOAT;
6440     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
6441     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
6442 BEGIN
6443     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6444     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
6445     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6446     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6447     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
6448
6449     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
6450
6451     IF NOT FOUND THEN
6452         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
6453     ELSE
6454         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
6455     END IF;
6456
6457     LOOP 
6458         -- for each potential matchpoint for this ou and group ...
6459         FOR current_mp IN
6460             SELECT    m.*
6461               FROM    config.hold_matrix_matchpoint m
6462               WHERE    m.requestor_grp = current_requestor_group.id AND m.active
6463               ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
6464                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
6465                     CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
6466                     CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
6467                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
6468                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
6469
6470             current_mp_weight := 5.0;
6471
6472             IF current_mp.circ_modifier IS NOT NULL THEN
6473                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6474             END IF;
6475
6476             IF current_mp.marc_type IS NOT NULL THEN
6477                 IF item_object.circ_as_type IS NOT NULL THEN
6478                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6479                 ELSE
6480                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6481                 END IF;
6482             END IF;
6483
6484             IF current_mp.marc_form IS NOT NULL THEN
6485                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6486             END IF;
6487
6488             IF current_mp.marc_vr_format IS NOT NULL THEN
6489                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6490             END IF;
6491
6492             IF current_mp.juvenile_flag IS NOT NULL THEN
6493                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6494             END IF;
6495
6496             IF current_mp.ref_flag IS NOT NULL THEN
6497                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6498             END IF;
6499
6500
6501             -- caclulate the rule match weight
6502             IF current_mp.item_owning_ou IS NOT NULL THEN
6503                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
6504                 current_mp_weight := current_mp_weight - tmp_weight;
6505             END IF; 
6506
6507             IF current_mp.item_circ_ou IS NOT NULL THEN
6508                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
6509                 current_mp_weight := current_mp_weight - tmp_weight;
6510             END IF; 
6511
6512             IF current_mp.pickup_ou IS NOT NULL THEN
6513                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
6514                 current_mp_weight := current_mp_weight - tmp_weight;
6515             END IF; 
6516
6517             IF current_mp.request_ou IS NOT NULL THEN
6518                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
6519                 current_mp_weight := current_mp_weight - tmp_weight;
6520             END IF; 
6521
6522             IF current_mp.user_home_ou IS NOT NULL THEN
6523                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
6524                 current_mp_weight := current_mp_weight - tmp_weight;
6525             END IF; 
6526
6527             -- set the matchpoint if we found the best one
6528             IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
6529                 matchpoint = current_mp;
6530                 matchpoint_weight = current_mp_weight;
6531             END IF;
6532
6533         END LOOP;
6534
6535         EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6536
6537         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
6538     END LOOP;
6539
6540     RETURN matchpoint.id;
6541 END;
6542 $func$ LANGUAGE plpgsql;
6543
6544 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$
6545 DECLARE
6546     matchpoint_id        INT;
6547     user_object        actor.usr%ROWTYPE;
6548     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
6549     standing_penalty    config.standing_penalty%ROWTYPE;
6550     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
6551     transit_source        actor.org_unit%ROWTYPE;
6552     item_object        asset.copy%ROWTYPE;
6553     ou_skip              actor.org_unit_setting%ROWTYPE;
6554     result            action.matrix_test_result;
6555     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
6556     hold_count        INT;
6557     hold_transit_prox    INT;
6558     frozen_hold_count    INT;
6559     context_org_list    INT[];
6560     done            BOOL := FALSE;
6561 BEGIN
6562     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6563     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
6564
6565     result.success := TRUE;
6566
6567     -- Fail if we couldn't find a user
6568     IF user_object.id IS NULL THEN
6569         result.fail_part := 'no_user';
6570         result.success := FALSE;
6571         done := TRUE;
6572         RETURN NEXT result;
6573         RETURN;
6574     END IF;
6575
6576     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6577
6578     -- Fail if we couldn't find a copy
6579     IF item_object.id IS NULL THEN
6580         result.fail_part := 'no_item';
6581         result.success := FALSE;
6582         done := TRUE;
6583         RETURN NEXT result;
6584         RETURN;
6585     END IF;
6586
6587     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
6588     result.matchpoint := matchpoint_id;
6589
6590     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
6591
6592     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
6593     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
6594         result.fail_part := 'circ.holds.target_skip_me';
6595         result.success := FALSE;
6596         done := TRUE;
6597         RETURN NEXT result;
6598         RETURN;
6599     END IF;
6600
6601     -- Fail if user is barred
6602     IF user_object.barred IS TRUE THEN
6603         result.fail_part := 'actor.usr.barred';
6604         result.success := FALSE;
6605         done := TRUE;
6606         RETURN NEXT result;
6607         RETURN;
6608     END IF;
6609
6610     -- Fail if we couldn't find any matchpoint (requires a default)
6611     IF matchpoint_id IS NULL THEN
6612         result.fail_part := 'no_matchpoint';
6613         result.success := FALSE;
6614         done := TRUE;
6615         RETURN NEXT result;
6616         RETURN;
6617     END IF;
6618
6619     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
6620
6621     IF hold_test.holdable IS FALSE THEN
6622         result.fail_part := 'config.hold_matrix_test.holdable';
6623         result.success := FALSE;
6624         done := TRUE;
6625         RETURN NEXT result;
6626     END IF;
6627
6628     IF hold_test.transit_range IS NOT NULL THEN
6629         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
6630         IF hold_test.distance_is_from_owner THEN
6631             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;
6632         ELSE
6633             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
6634         END IF;
6635
6636         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
6637
6638         IF NOT FOUND THEN
6639             result.fail_part := 'transit_range';
6640             result.success := FALSE;
6641             done := TRUE;
6642             RETURN NEXT result;
6643         END IF;
6644     END IF;
6645  
6646     IF NOT retargetting THEN
6647         FOR standing_penalty IN
6648             SELECT  DISTINCT csp.*
6649               FROM  actor.usr_standing_penalty usp
6650                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6651               WHERE usr = match_user
6652                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6653                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6654                     AND csp.block_list LIKE '%HOLD%' LOOP
6655     
6656             result.fail_part := standing_penalty.name;
6657             result.success := FALSE;
6658             done := TRUE;
6659             RETURN NEXT result;
6660         END LOOP;
6661     
6662         IF hold_test.stop_blocked_user IS TRUE THEN
6663             FOR standing_penalty IN
6664                 SELECT  DISTINCT csp.*
6665                   FROM  actor.usr_standing_penalty usp
6666                         JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6667                   WHERE usr = match_user
6668                         AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6669                         AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6670                         AND csp.block_list LIKE '%CIRC%' LOOP
6671         
6672                 result.fail_part := standing_penalty.name;
6673                 result.success := FALSE;
6674                 done := TRUE;
6675                 RETURN NEXT result;
6676             END LOOP;
6677         END IF;
6678     
6679         IF hold_test.max_holds IS NOT NULL THEN
6680             SELECT    INTO hold_count COUNT(*)
6681               FROM    action.hold_request
6682               WHERE    usr = match_user
6683                 AND fulfillment_time IS NULL
6684                 AND cancel_time IS NULL
6685                 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
6686     
6687             IF hold_count >= hold_test.max_holds THEN
6688                 result.fail_part := 'config.hold_matrix_test.max_holds';
6689                 result.success := FALSE;
6690                 done := TRUE;
6691                 RETURN NEXT result;
6692             END IF;
6693         END IF;
6694     END IF;
6695
6696     IF item_object.age_protect IS NOT NULL THEN
6697         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
6698
6699         IF item_object.create_date + age_protect_object.age > NOW() THEN
6700             IF hold_test.distance_is_from_owner THEN
6701                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
6702             ELSE
6703                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
6704             END IF;
6705
6706             IF hold_transit_prox > age_protect_object.prox THEN
6707                 result.fail_part := 'config.rule_age_hold_protect.prox';
6708                 result.success := FALSE;
6709                 done := TRUE;
6710                 RETURN NEXT result;
6711             END IF;
6712         END IF;
6713     END IF;
6714
6715     IF NOT done THEN
6716         RETURN NEXT result;
6717     END IF;
6718
6719     RETURN;
6720 END;
6721 $func$ LANGUAGE plpgsql;
6722
6723 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$
6724     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE);
6725 $func$ LANGUAGE SQL;
6726
6727 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$
6728     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
6729 $func$ LANGUAGE SQL;
6730
6731 -- New post-delete trigger to propagate deletions to parent(s)
6732
6733 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
6734 BEGIN
6735
6736     -- Having deleted a renewal, we can delete the original circulation (or a previous
6737     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
6738     -- deletion of any prior parents, etc. recursively.
6739
6740     IF OLD.parent_circ IS NOT NULL THEN
6741         DELETE FROM action.circulation
6742         WHERE id = OLD.parent_circ;
6743     END IF;
6744
6745     RETURN OLD;
6746 END;
6747 $$ LANGUAGE 'plpgsql';
6748
6749 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
6750 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
6751
6752 -- This only gets inserted if there are no other id > 100 billing types
6753 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;
6754 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
6755
6756 -- Populate xact_type column in the materialized version of billable_xact_summary
6757
6758 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
6759 BEGIN
6760         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
6761                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
6762         RETURN NEW;
6763 END;
6764 $$ LANGUAGE PLPGSQL;
6765  
6766 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
6767 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
6768  
6769 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
6770 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
6771
6772 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
6773     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;
6774
6775 -- Generate the equivalent of compound subject entries from the existing rows
6776 -- so that we don't have to laboriously reindex them
6777
6778 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
6779 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
6780 --
6781 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
6782 --
6783 --INSERT INTO metabib.subject_field_entry (source, field, value)
6784 --    SELECT source, (
6785 --            SELECT id 
6786 --            FROM config.metabib_field
6787 --            WHERE field_class = 'subject' AND name = 'complete'
6788 --        ), 
6789 --        ARRAY_TO_STRING ( 
6790 --            ARRAY (
6791 --                SELECT value 
6792 --                FROM metabib.subject_field_entry msfe
6793 --                WHERE msfe.source = groupee.source
6794 --                ORDER BY source 
6795 --            ), ' ' 
6796 --        ) AS grouped
6797 --    FROM ( 
6798 --        SELECT source
6799 --        FROM metabib.subject_field_entry
6800 --        GROUP BY source
6801 --    ) AS groupee;
6802
6803 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
6804 DECLARE
6805         prev_billing    money.billing%ROWTYPE;
6806         old_billing     money.billing%ROWTYPE;
6807 BEGIN
6808         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
6809         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
6810
6811         IF OLD.id = old_billing.id THEN
6812                 UPDATE  money.materialized_billable_xact_summary
6813                   SET   last_billing_ts = prev_billing.billing_ts,
6814                         last_billing_note = prev_billing.note,
6815                         last_billing_type = prev_billing.billing_type
6816                   WHERE id = OLD.xact;
6817         END IF;
6818
6819         IF NOT OLD.voided THEN
6820                 UPDATE  money.materialized_billable_xact_summary
6821                   SET   total_owed = total_owed - OLD.amount,
6822                         balance_owed = balance_owed + OLD.amount
6823                   WHERE id = OLD.xact;
6824         END IF;
6825
6826         RETURN OLD;
6827 END;
6828 $$ LANGUAGE PLPGSQL;
6829
6830 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
6831 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
6832
6833 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
6834         use Unicode::Normalize;
6835         use Encode;
6836
6837         # When working with Unicode data, the first step is to decode it to
6838         # a byte string; after that, lowercasing is safe
6839         my $txt = lc(decode_utf8(shift));
6840         my $sf = shift;
6841
6842         $txt = NFD($txt);
6843         $txt =~ s/\pM+//go;     # Remove diacritics
6844
6845         $txt =~ s/\xE6/AE/go;   # Convert ae digraph
6846         $txt =~ s/\x{153}/OE/go;# Convert oe digraph
6847         $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
6848
6849         $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
6850         $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
6851
6852         $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;             # Convert Latin and Greek
6853         $txt =~ tr/\x{2113}\xF0\x{111}\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LDD /;     # Convert Misc
6854         $txt =~ tr/\'\[\]\|//d;                                                 # Remove Misc
6855
6856         if ($sf && $sf =~ /^a/o) {
6857                 my $commapos = index($txt,',');
6858                 if ($commapos > -1) {
6859                         if ($commapos != length($txt) - 1) {
6860                                 my @list = split /,/, $txt;
6861                                 my $first = shift @list;
6862                                 $txt = $first . ',' . join(' ', @list);
6863                         } else {
6864                                 $txt =~ s/,/ /go;
6865                         }
6866                 }
6867         } else {
6868                 $txt =~ s/,/ /go;
6869         }
6870
6871         $txt =~ s/\s+/ /go;     # Compress multiple spaces
6872         $txt =~ s/^\s+//o;      # Remove leading space
6873         $txt =~ s/\s+$//o;      # Remove trailing space
6874
6875         # Encoding the outgoing string is good practice, but not strictly
6876         # necessary in this case because we've stripped everything from it
6877         return encode_utf8($txt);
6878 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
6879
6880 -- Some handy functions, based on existing ones, to provide optional ingest normalization
6881
6882 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6883         SELECT SUBSTRING($1,$2);
6884 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6885
6886 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6887         SELECT SUBSTRING($1,1,$2);
6888 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6889
6890 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
6891         SELECT public.naco_normalize($1,'a');
6892 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6893
6894 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
6895         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
6896 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6897
6898 -- And ... a table in which to register them
6899
6900 CREATE TABLE config.index_normalizer (
6901         id              SERIAL  PRIMARY KEY,
6902         name            TEXT    UNIQUE NOT NULL,
6903         description     TEXT,
6904         func            TEXT    NOT NULL,
6905         param_count     INT     NOT NULL DEFAULT 0
6906 );
6907
6908 CREATE TABLE config.metabib_field_index_norm_map (
6909         id      SERIAL  PRIMARY KEY,
6910         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6911         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6912         params  TEXT,
6913         pos     INT     NOT NULL DEFAULT 0
6914 );
6915
6916 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6917         'NACO Normalize',
6918         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
6919         'naco_normalize',
6920         0
6921 );
6922
6923 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6924         'Normalize date range',
6925         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
6926         'split_date_range',
6927         1
6928 );
6929
6930 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6931         'NACO Normalize -- retain first comma',
6932         '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.',
6933         'naco_normalize_keep_comma',
6934         0
6935 );
6936
6937 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6938         'Strip Diacritics',
6939         'Convert text to NFD form and remove non-spacing combining marks.',
6940         'remove_diacritics',
6941         0
6942 );
6943
6944 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6945         'Up-case',
6946         'Convert text upper case.',
6947         'uppercase',
6948         0
6949 );
6950
6951 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6952         'Down-case',
6953         'Convert text lower case.',
6954         'lowercase',
6955         0
6956 );
6957
6958 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6959         'Extract Dewey-like number',
6960         'Extract a string of numeric characters ther resembles a DDC number.',
6961         'call_number_dewey',
6962         0
6963 );
6964
6965 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6966         'Left truncation',
6967         'Discard the specified number of characters from the left side of the string.',
6968         'left_trunc',
6969         1
6970 );
6971
6972 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6973         'Right truncation',
6974         'Include only the specified number of characters from the left side of the string.',
6975         'right_trunc',
6976         1
6977 );
6978
6979 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6980         'First word',
6981         'Include only the first space-separated word of a string.',
6982         'first_word',
6983         0
6984 );
6985
6986 INSERT INTO config.metabib_field_index_norm_map (field,norm)
6987         SELECT  m.id,
6988                 i.id
6989           FROM  config.metabib_field m,
6990                 config.index_normalizer i
6991           WHERE i.func IN ('naco_normalize','split_date_range');
6992
6993 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
6994 DECLARE
6995     normalizer      RECORD;
6996     value           TEXT := '';
6997 BEGIN
6998
6999     value := NEW.value;
7000
7001     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7002         FOR normalizer IN
7003             SELECT  n.func AS func,
7004                     n.param_count AS param_count,
7005                     m.params AS params
7006               FROM  config.index_normalizer n
7007                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7008               WHERE field = NEW.field AND m.pos < 0
7009               ORDER BY m.pos LOOP
7010                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7011                     quote_literal( value ) ||
7012                     CASE
7013                         WHEN normalizer.param_count > 0
7014                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7015                             ELSE ''
7016                         END ||
7017                     ')' INTO value;
7018
7019         END LOOP;
7020
7021         NEW.value := value;
7022     END IF;
7023
7024     IF NEW.index_vector = ''::tsvector THEN
7025         RETURN NEW;
7026     END IF;
7027
7028     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7029         FOR normalizer IN
7030             SELECT  n.func AS func,
7031                     n.param_count AS param_count,
7032                     m.params AS params
7033               FROM  config.index_normalizer n
7034                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7035               WHERE field = NEW.field AND m.pos >= 0
7036               ORDER BY m.pos LOOP
7037                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7038                     quote_literal( value ) ||
7039                     CASE
7040                         WHEN normalizer.param_count > 0
7041                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7042                             ELSE ''
7043                         END ||
7044                     ')' INTO value;
7045
7046         END LOOP;
7047     END IF;
7048
7049     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
7050         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
7051     ELSE
7052         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
7053     END IF;
7054
7055     RETURN NEW;
7056 END;
7057 $$ LANGUAGE PLPGSQL;
7058
7059 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7060
7061 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7062
7063 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7064     SELECT  ARRAY_TO_STRING(
7065                 oils_xpath(
7066                     $1 ||
7067                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
7068                     $2,
7069                     $4
7070                 ),
7071                 $3
7072             );
7073 $func$ LANGUAGE SQL IMMUTABLE;
7074
7075 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
7076     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
7077 $func$ LANGUAGE SQL IMMUTABLE;
7078
7079 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7080     SELECT oils_xpath_string( $1, $2, '', $3 );
7081 $func$ LANGUAGE SQL IMMUTABLE;
7082
7083 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
7084     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
7085 $func$ LANGUAGE SQL IMMUTABLE;
7086
7087 CREATE TYPE metabib.field_entry_template AS (
7088         field_class     TEXT,
7089         field           INT,
7090         source          BIGINT,
7091         value           TEXT
7092 );
7093
7094 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7095   use strict;
7096
7097   use XML::LibXSLT;
7098   use XML::LibXML;
7099
7100   my $doc = shift;
7101   my $xslt = shift;
7102
7103   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7104   # methods of parsing XML documents and stylesheets, in the hopes of broader
7105   # compatibility with distributions
7106   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7107
7108   # Cache the XML parser, if we do not already have one
7109   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7110     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7111
7112   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7113
7114   # Cache the XSLT processor, if we do not already have one
7115   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7116     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7117
7118   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7119     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7120
7121   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7122     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7123
7124   return $stylesheet->output_string(
7125     $stylesheet->transform(
7126       $parser->parse_string($doc)
7127     )
7128   );
7129
7130 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7131
7132 -- Add two columns so that the following function will compile.
7133 -- Eventually the label column will be NOT NULL, but not yet.
7134 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7135 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7136
7137 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7138 DECLARE
7139     bib     biblio.record_entry%ROWTYPE;
7140     idx     config.metabib_field%ROWTYPE;
7141     xfrm        config.xml_transform%ROWTYPE;
7142     prev_xfrm   TEXT;
7143     transformed_xml TEXT;
7144     xml_node    TEXT;
7145     xml_node_list   TEXT[];
7146     facet_text  TEXT;
7147     raw_text    TEXT;
7148     curr_text   TEXT;
7149     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7150     output_row  metabib.field_entry_template%ROWTYPE;
7151 BEGIN
7152
7153     -- Get the record
7154     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7155
7156     -- Loop over the indexing entries
7157     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7158
7159         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7160
7161         -- See if we can skip the XSLT ... it's expensive
7162         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7163             -- Can't skip the transform
7164             IF xfrm.xslt <> '---' THEN
7165                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7166             ELSE
7167                 transformed_xml := bib.marc;
7168             END IF;
7169
7170             prev_xfrm := xfrm.name;
7171         END IF;
7172
7173         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7174
7175         raw_text := NULL;
7176         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7177             CONTINUE WHEN xml_node !~ E'^\\s*<';
7178
7179             curr_text := ARRAY_TO_STRING(
7180                 oils_xpath( '//text()',
7181                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7182                         REGEXP_REPLACE( -- This escapes embeded <s
7183                             xml_node,
7184                             $re$(>[^<]+)(<)([^>]+<)$re$,
7185                             E'\\1&lt;\\3',
7186                             'g'
7187                         ),
7188                         '&(?!amp;)',
7189                         '&amp;',
7190                         'g'
7191                     )
7192                 ),
7193                 ' '
7194             );
7195
7196             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7197
7198             IF raw_text IS NOT NULL THEN
7199                 raw_text := raw_text || joiner;
7200             END IF;
7201
7202             raw_text := COALESCE(raw_text,'') || curr_text;
7203
7204             -- insert raw node text for faceting
7205             IF idx.facet_field THEN
7206
7207                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7208                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7209                 ELSE
7210                     facet_text := curr_text;
7211                 END IF;
7212
7213                 output_row.field_class = idx.field_class;
7214                 output_row.field = -1 * idx.id;
7215                 output_row.source = rid;
7216                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7217
7218                 RETURN NEXT output_row;
7219             END IF;
7220
7221         END LOOP;
7222
7223         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7224
7225         -- insert combined node text for searching
7226         IF idx.search_field THEN
7227             output_row.field_class = idx.field_class;
7228             output_row.field = idx.id;
7229             output_row.source = rid;
7230             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7231
7232             RETURN NEXT output_row;
7233         END IF;
7234
7235     END LOOP;
7236
7237 END;
7238 $func$ LANGUAGE PLPGSQL;
7239
7240 -- default to a space joiner
7241 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7242         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7243 $func$ LANGUAGE SQL;
7244
7245 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7246
7247 use MARC::Record;
7248 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7249
7250 my $xml = shift;
7251 my $r = MARC::Record->new_from_xml( $xml );
7252
7253 return_next( { tag => 'LDR', value => $r->leader } );
7254
7255 for my $f ( $r->fields ) {
7256     if ($f->is_control_field) {
7257         return_next({ tag => $f->tag, value => $f->data });
7258     } else {
7259         for my $s ($f->subfields) {
7260             return_next({
7261                 tag      => $f->tag,
7262                 ind1     => $f->indicator(1),
7263                 ind2     => $f->indicator(2),
7264                 subfield => $s->[0],
7265                 value    => $s->[1]
7266             });
7267
7268             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7269                 my $trim = $f->indicator(2) || 0;
7270                 return_next({
7271                     tag      => 'tnf',
7272                     ind1     => $f->indicator(1),
7273                     ind2     => $f->indicator(2),
7274                     subfield => 'a',
7275                     value    => substr( $s->[1], $trim )
7276                 });
7277             }
7278         }
7279     }
7280 }
7281
7282 return undef;
7283
7284 $func$ LANGUAGE PLPERLU;
7285
7286 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7287 DECLARE
7288     bib biblio.record_entry%ROWTYPE;
7289     output  metabib.full_rec%ROWTYPE;
7290     field   RECORD;
7291 BEGIN
7292     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7293
7294     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7295         output.record := rid;
7296         output.ind1 := field.ind1;
7297         output.ind2 := field.ind2;
7298         output.tag := field.tag;
7299         output.subfield := field.subfield;
7300         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7301             output.value := naco_normalize(field.value, field.subfield);
7302         ELSE
7303             output.value := field.value;
7304         END IF;
7305
7306         CONTINUE WHEN output.value IS NULL;
7307
7308         RETURN NEXT output;
7309     END LOOP;
7310 END;
7311 $func$ LANGUAGE PLPGSQL;
7312
7313 -- functions to create auditor objects
7314
7315 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7316 BEGIN
7317     EXECUTE $$
7318         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7319     $$;
7320         RETURN TRUE;
7321 END;
7322 $creator$ LANGUAGE 'plpgsql';
7323
7324 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7325 BEGIN
7326     EXECUTE $$
7327         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7328             audit_id    BIGINT                          PRIMARY KEY,
7329             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7330             audit_action        TEXT                            NOT NULL,
7331             LIKE $$ || sch || $$.$$ || tbl || $$
7332         );
7333     $$;
7334         RETURN TRUE;
7335 END;
7336 $creator$ LANGUAGE 'plpgsql';
7337
7338 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7339 BEGIN
7340     EXECUTE $$
7341         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7342         RETURNS TRIGGER AS $func$
7343         BEGIN
7344             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7345                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7346                     now(),
7347                     SUBSTR(TG_OP,1,1),
7348                     OLD.*;
7349             RETURN NULL;
7350         END;
7351         $func$ LANGUAGE 'plpgsql';
7352     $$;
7353         RETURN TRUE;
7354 END;
7355 $creator$ LANGUAGE 'plpgsql';
7356
7357 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7358 BEGIN
7359     EXECUTE $$
7360         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7361             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7362             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7363     $$;
7364         RETURN TRUE;
7365 END;
7366 $creator$ LANGUAGE 'plpgsql';
7367
7368 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7369 BEGIN
7370     EXECUTE $$
7371         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7372             SELECT      -1, now() as audit_time, '-' as audit_action, *
7373               FROM      $$ || sch || $$.$$ || tbl || $$
7374                 UNION ALL
7375             SELECT      *
7376               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7377     $$;
7378         RETURN TRUE;
7379 END;
7380 $creator$ LANGUAGE 'plpgsql';
7381
7382 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7383
7384 -- The main event
7385
7386 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7387 BEGIN
7388     PERFORM auditor.create_auditor_seq(sch, tbl);
7389     PERFORM auditor.create_auditor_history(sch, tbl);
7390     PERFORM auditor.create_auditor_func(sch, tbl);
7391     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7392     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7393     RETURN TRUE;
7394 END;
7395 $creator$ LANGUAGE 'plpgsql';
7396
7397 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7398
7399 ALTER TABLE action.hold_request
7400 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7401
7402 ALTER TABLE action.hold_request
7403 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7404
7405 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7406
7407 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7408
7409 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7410
7411 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7412
7413 -- Add claims_never_checked_out_count to actor.usr, related history
7414
7415 ALTER TABLE actor.usr ADD COLUMN
7416         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7417
7418 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7419         claims_never_checked_out_count INT;
7420
7421 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7422
7423 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7424
7425 -----------
7426
7427 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7428 BEGIN
7429         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7430                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7431                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7432                 END IF;
7433                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7434                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7435                 END IF;
7436                 IF NEW.stop_fines = 'LOST' THEN
7437                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7438                 END IF;
7439         END IF;
7440         RETURN NEW;
7441 END;
7442 $$ LANGUAGE 'plpgsql';
7443
7444 -- Create new table acq.fund_allocation_percent
7445 -- Populate it from acq.fund_allocation
7446 -- Convert all percentages to amounts in acq.fund_allocation
7447
7448 CREATE TABLE acq.fund_allocation_percent
7449 (
7450     id                   SERIAL            PRIMARY KEY,
7451     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7452                                                DEFERRABLE INITIALLY DEFERRED,
7453     org                  INT               NOT NULL REFERENCES actor.org_unit
7454                                                DEFERRABLE INITIALLY DEFERRED,
7455     fund_code            TEXT,
7456     percent              NUMERIC           NOT NULL,
7457     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7458                                                DEFERRABLE INITIALLY DEFERRED,
7459     note                 TEXT,
7460     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7461     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7462     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7463 );
7464
7465 -- Trigger function to validate combination of org_unit and fund_code
7466
7467 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7468 RETURNS TRIGGER AS $$
7469 --
7470 DECLARE
7471 --
7472 dummy int := 0;
7473 --
7474 BEGIN
7475     SELECT
7476         1
7477     INTO
7478         dummy
7479     FROM
7480         acq.fund
7481     WHERE
7482         org = NEW.org
7483         AND code = NEW.fund_code
7484         LIMIT 1;
7485     --
7486     IF dummy = 1 then
7487         RETURN NEW;
7488     ELSE
7489         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7490     END IF;
7491 END;
7492 $$ LANGUAGE plpgsql;
7493
7494 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7495     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7496     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7497
7498 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7499 RETURNS TRIGGER AS $$
7500 DECLARE
7501 --
7502 total_percent numeric;
7503 --
7504 BEGIN
7505     SELECT
7506         sum( percent )
7507     INTO
7508         total_percent
7509     FROM
7510         acq.fund_allocation_percent AS fap
7511     WHERE
7512         fap.funding_source = NEW.funding_source;
7513     --
7514     IF total_percent > 100 THEN
7515         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7516             NEW.funding_source;
7517     ELSE
7518         RETURN NEW;
7519     END IF;
7520 END;
7521 $$ LANGUAGE plpgsql;
7522
7523 CREATE TRIGGER acqfap_limit_100_trig
7524     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7525     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7526
7527 -- Populate new table from acq.fund_allocation
7528
7529 INSERT INTO acq.fund_allocation_percent
7530 (
7531     funding_source,
7532     org,
7533     fund_code,
7534     percent,
7535     allocator,
7536     note,
7537     create_time
7538 )
7539     SELECT
7540         fa.funding_source,
7541         fund.org,
7542         fund.code,
7543         fa.percent,
7544         fa.allocator,
7545         fa.note,
7546         fa.create_time
7547     FROM
7548         acq.fund_allocation AS fa
7549             INNER JOIN acq.fund AS fund
7550                 ON ( fa.fund = fund.id )
7551     WHERE
7552         fa.percent is not null
7553     ORDER BY
7554         fund.org;
7555
7556 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7557
7558 -- Algorithm to apply to each funding source:
7559
7560 -- 1. Add up the credits.
7561 -- 2. Add up the percentages.
7562 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7563 --    fractional cents from the result.  This is the total amount to be allocated.
7564 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7565 --    fractional cents to get a preliminary amount.
7566 -- 5. Add up the preliminary amounts for all the allocations.
7567 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7568 --    number of residual cents (resulting from having dropped fractional cents) that
7569 --    must be distributed across the funds in order to make the total of the amounts
7570 --    match the total allocation.
7571 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7572 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7573 --    for each successive fund, until all the residual cents have been exhausted.
7574
7575 -- Result: the sum of the individual allocations now equals the total to be allocated,
7576 -- to the penny.  The individual amounts match the percentages as closely as possible,
7577 -- given the constraint that the total must match.
7578
7579 CREATE OR REPLACE FUNCTION acq.apply_percents()
7580 RETURNS VOID AS $$
7581 declare
7582 --
7583 tot              RECORD;
7584 fund             RECORD;
7585 tot_cents        INTEGER;
7586 src              INTEGER;
7587 id               INTEGER[];
7588 curr_id          INTEGER;
7589 pennies          NUMERIC[];
7590 curr_amount      NUMERIC;
7591 i                INTEGER;
7592 total_of_floors  INTEGER;
7593 total_percent    NUMERIC;
7594 total_allocation INTEGER;
7595 residue          INTEGER;
7596 --
7597 begin
7598         RAISE NOTICE 'Applying percents';
7599         FOR tot IN
7600                 SELECT
7601                         fsrc.funding_source,
7602                         sum( fsrc.amount ) AS total
7603                 FROM
7604                         acq.funding_source_credit AS fsrc
7605                 WHERE fsrc.funding_source IN
7606                         ( SELECT DISTINCT fa.funding_source
7607                           FROM acq.fund_allocation AS fa
7608                           WHERE fa.percent IS NOT NULL )
7609                 GROUP BY
7610                         fsrc.funding_source
7611         LOOP
7612                 tot_cents = floor( tot.total * 100 );
7613                 src = tot.funding_source;
7614                 RAISE NOTICE 'Funding source % total %',
7615                         src, tot_cents;
7616                 i := 0;
7617                 total_of_floors := 0;
7618                 total_percent := 0;
7619                 --
7620                 FOR fund in
7621                         SELECT
7622                                 fa.id,
7623                                 fa.percent,
7624                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7625                         FROM
7626                                 acq.fund_allocation AS fa
7627                         WHERE
7628                                 fa.funding_source = src
7629                                 AND fa.percent IS NOT NULL
7630                         ORDER BY
7631                                 mod( fa.percent * tot_cents / 100, 1 ),
7632                                 fa.fund,
7633                                 fa.id
7634                 LOOP
7635                         RAISE NOTICE '   %: %',
7636                                 fund.id,
7637                                 fund.floor_pennies;
7638                         i := i + 1;
7639                         id[i] = fund.id;
7640                         pennies[i] = fund.floor_pennies;
7641                         total_percent := total_percent + fund.percent;
7642                         total_of_floors := total_of_floors + pennies[i];
7643                 END LOOP;
7644                 total_allocation := floor( total_percent * tot_cents /100 );
7645                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7646                 residue := total_allocation - total_of_floors;
7647                 RAISE NOTICE 'Residue: %', residue;
7648                 --
7649                 -- Post the calculated amounts, revising as needed to
7650                 -- distribute the rounding error
7651                 --
7652                 WHILE i > 0 LOOP
7653                         IF residue > 0 THEN
7654                                 pennies[i] = pennies[i] + 1;
7655                                 residue := residue - 1;
7656                         END IF;
7657                         --
7658                         -- Post amount
7659                         --
7660                         curr_id     := id[i];
7661                         curr_amount := trunc( pennies[i] / 100, 2 );
7662                         --
7663                         UPDATE
7664                                 acq.fund_allocation AS fa
7665                         SET
7666                                 amount = curr_amount,
7667                                 percent = NULL
7668                         WHERE
7669                                 fa.id = curr_id;
7670                         --
7671                         RAISE NOTICE '   ID % and amount %',
7672                                 curr_id,
7673                                 curr_amount;
7674                         i = i - 1;
7675                 END LOOP;
7676         END LOOP;
7677 end;
7678 $$ LANGUAGE 'plpgsql';
7679
7680 -- Run the temporary function
7681
7682 select * from acq.apply_percents();
7683
7684 -- Drop the temporary function now that we're done with it
7685
7686 DROP FUNCTION IF EXISTS acq.apply_percents();
7687
7688 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7689
7690 -- If the following step fails, it's probably because there are still some non-null percent values in
7691 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7692 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7693 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7694 -- slipped in afterwards.
7695
7696 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7697 -- procedure acq.apply_percents() as defined above.
7698
7699 ALTER TABLE acq.fund_allocation
7700 ALTER COLUMN amount SET NOT NULL;
7701
7702 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7703     SELECT  fund,
7704             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7705     FROM acq.fund_allocation a
7706          JOIN acq.fund f ON (a.fund = f.id)
7707          JOIN acq.funding_source s ON (a.funding_source = s.id)
7708     GROUP BY 1;
7709
7710 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7711     SELECT  funding_source,
7712             SUM(a.amount)::NUMERIC(100,2) AS amount
7713     FROM  acq.fund_allocation a
7714     GROUP BY 1;
7715
7716 ALTER TABLE acq.fund_allocation
7717 DROP COLUMN percent;
7718
7719 CREATE TABLE asset.copy_location_order
7720 (
7721         id              SERIAL           PRIMARY KEY,
7722         location        INT              NOT NULL
7723                                              REFERENCES asset.copy_location
7724                                              ON DELETE CASCADE
7725                                              DEFERRABLE INITIALLY DEFERRED,
7726         org             INT              NOT NULL
7727                                              REFERENCES actor.org_unit
7728                                              ON DELETE CASCADE
7729                                              DEFERRABLE INITIALLY DEFERRED,
7730         position        INT              NOT NULL DEFAULT 0,
7731         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7732 );
7733
7734 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7735
7736 -- If you ran this before its most recent incarnation:
7737 -- delete from config.upgrade_log where version = '0328';
7738 -- alter table money.credit_card_payment drop column cc_name;
7739
7740 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7741 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7742
7743 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$
7744 DECLARE
7745     current_group    permission.grp_tree%ROWTYPE;
7746     user_object    actor.usr%ROWTYPE;
7747     item_object    asset.copy%ROWTYPE;
7748     cn_object    asset.call_number%ROWTYPE;
7749     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7750     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7751     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7752 BEGIN
7753     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7754     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7755     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7756     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7757     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7758
7759     LOOP 
7760         -- for each potential matchpoint for this ou and group ...
7761         FOR current_mp IN
7762             SELECT  m.*
7763               FROM  config.circ_matrix_matchpoint m
7764                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
7765                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
7766               WHERE m.grp = current_group.id
7767                     AND m.active
7768                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
7769                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
7770               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
7771                     CASE WHEN m.copy_owning_lib IS NOT NULL
7772                         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 )
7773                         ELSE 0
7774                     END +
7775                     CASE WHEN m.copy_circ_lib IS NOT NULL
7776                         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 )
7777                         ELSE 0
7778                     END +
7779                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
7780                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
7781                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
7782                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
7783                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
7784                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
7785                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
7786                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
7787                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
7788
7789             IF current_mp.is_renewal IS NOT NULL THEN
7790                 CONTINUE WHEN current_mp.is_renewal <> renewal;
7791             END IF;
7792
7793             IF current_mp.circ_modifier IS NOT NULL THEN
7794                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
7795             END IF;
7796
7797             IF current_mp.marc_type IS NOT NULL THEN
7798                 IF item_object.circ_as_type IS NOT NULL THEN
7799                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
7800                 ELSE
7801                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
7802                 END IF;
7803             END IF;
7804
7805             IF current_mp.marc_form IS NOT NULL THEN
7806                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
7807             END IF;
7808
7809             IF current_mp.marc_vr_format IS NOT NULL THEN
7810                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
7811             END IF;
7812
7813             IF current_mp.ref_flag IS NOT NULL THEN
7814                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
7815             END IF;
7816
7817             IF current_mp.juvenile_flag IS NOT NULL THEN
7818                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
7819             END IF;
7820
7821             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
7822                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
7823             END IF;
7824
7825             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
7826                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
7827             END IF;
7828
7829
7830             -- everything was undefined or matched
7831             matchpoint = current_mp;
7832
7833             EXIT WHEN matchpoint.id IS NOT NULL;
7834         END LOOP;
7835
7836         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
7837
7838         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
7839     END LOOP;
7840
7841     RETURN matchpoint;
7842 END;
7843 $func$ LANGUAGE plpgsql;
7844
7845 CREATE TYPE action.hold_stats AS (
7846     hold_count              INT,
7847     copy_count              INT,
7848     available_count         INT,
7849     total_copy_ratio        FLOAT,
7850     available_copy_ratio    FLOAT
7851 );
7852
7853 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
7854 DECLARE
7855     output          action.hold_stats%ROWTYPE;
7856     hold_count      INT := 0;
7857     copy_count      INT := 0;
7858     available_count INT := 0;
7859     hold_map_data   RECORD;
7860 BEGIN
7861
7862     output.hold_count := 0;
7863     output.copy_count := 0;
7864     output.available_count := 0;
7865
7866     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
7867       FROM  action.hold_copy_map m
7868             JOIN action.hold_request h ON (m.hold = h.id)
7869       WHERE m.target_copy = copy_id
7870             AND NOT h.frozen;
7871
7872     output.hold_count := hold_count;
7873
7874     IF output.hold_count > 0 THEN
7875         FOR hold_map_data IN
7876             SELECT  DISTINCT m.target_copy,
7877                     acp.status
7878               FROM  action.hold_copy_map m
7879                     JOIN asset.copy acp ON (m.target_copy = acp.id)
7880                     JOIN action.hold_request h ON (m.hold = h.id)
7881               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
7882         LOOP
7883             output.copy_count := output.copy_count + 1;
7884             IF hold_map_data.status IN (0,7,12) THEN
7885                 output.available_count := output.available_count + 1;
7886             END IF;
7887         END LOOP;
7888         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
7889         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
7890
7891     END IF;
7892
7893     RETURN output;
7894
7895 END;
7896 $func$ LANGUAGE PLPGSQL;
7897
7898 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
7899 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
7900
7901 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
7902
7903 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7904 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7905
7906 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
7907     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
7908     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
7909     copy_owning_lib
7910 );
7911
7912 -- Return the correct fail_part when the item can't be found
7913 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$
7914 DECLARE
7915     user_object        actor.usr%ROWTYPE;
7916     standing_penalty    config.standing_penalty%ROWTYPE;
7917     item_object        asset.copy%ROWTYPE;
7918     item_status_object    config.copy_status%ROWTYPE;
7919     item_location_object    asset.copy_location%ROWTYPE;
7920     result            action.matrix_test_result;
7921     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
7922     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
7923     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
7924     hold_ratio          action.hold_stats%ROWTYPE;
7925     penalty_type         TEXT;
7926     tmp_grp         INT;
7927     items_out        INT;
7928     context_org_list        INT[];
7929     done            BOOL := FALSE;
7930 BEGIN
7931     result.success := TRUE;
7932
7933     -- Fail if the user is BARRED
7934     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7935
7936     -- Fail if we couldn't find the user 
7937     IF user_object.id IS NULL THEN
7938         result.fail_part := 'no_user';
7939         result.success := FALSE;
7940         done := TRUE;
7941         RETURN NEXT result;
7942         RETURN;
7943     END IF;
7944
7945     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7946
7947     -- Fail if we couldn't find the item 
7948     IF item_object.id IS NULL THEN
7949         result.fail_part := 'no_item';
7950         result.success := FALSE;
7951         done := TRUE;
7952         RETURN NEXT result;
7953         RETURN;
7954     END IF;
7955
7956     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
7957     result.matchpoint := circ_test.id;
7958
7959     -- Fail if we couldn't find a matchpoint
7960     IF result.matchpoint IS NULL THEN
7961         result.fail_part := 'no_matchpoint';
7962         result.success := FALSE;
7963         done := TRUE;
7964         RETURN NEXT result;
7965     END IF;
7966
7967     IF user_object.barred IS TRUE THEN
7968         result.fail_part := 'actor.usr.barred';
7969         result.success := FALSE;
7970         done := TRUE;
7971         RETURN NEXT result;
7972     END IF;
7973
7974     -- Fail if the item can't circulate
7975     IF item_object.circulate IS FALSE THEN
7976         result.fail_part := 'asset.copy.circulate';
7977         result.success := FALSE;
7978         done := TRUE;
7979         RETURN NEXT result;
7980     END IF;
7981
7982     -- Fail if the item isn't in a circulateable status on a non-renewal
7983     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
7984         result.fail_part := 'asset.copy.status';
7985         result.success := FALSE;
7986         done := TRUE;
7987         RETURN NEXT result;
7988     ELSIF renewal AND item_object.status <> 1 THEN
7989         result.fail_part := 'asset.copy.status';
7990         result.success := FALSE;
7991         done := TRUE;
7992         RETURN NEXT result;
7993     END IF;
7994
7995     -- Fail if the item can't circulate because of the shelving location
7996     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
7997     IF item_location_object.circulate IS FALSE THEN
7998         result.fail_part := 'asset.copy_location.circulate';
7999         result.success := FALSE;
8000         done := TRUE;
8001         RETURN NEXT result;
8002     END IF;
8003
8004     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
8005
8006     -- Fail if the test is set to hard non-circulating
8007     IF circ_test.circulate IS FALSE THEN
8008         result.fail_part := 'config.circ_matrix_test.circulate';
8009         result.success := FALSE;
8010         done := TRUE;
8011         RETURN NEXT result;
8012     END IF;
8013
8014     -- Fail if the total copy-hold ratio is too low
8015     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
8016         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8017         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
8018             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8019             result.success := FALSE;
8020             done := TRUE;
8021             RETURN NEXT result;
8022         END IF;
8023     END IF;
8024
8025     -- Fail if the available copy-hold ratio is too low
8026     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
8027         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8028         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
8029             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8030             result.success := FALSE;
8031             done := TRUE;
8032             RETURN NEXT result;
8033         END IF;
8034     END IF;
8035
8036     IF renewal THEN
8037         penalty_type = '%RENEW%';
8038     ELSE
8039         penalty_type = '%CIRC%';
8040     END IF;
8041
8042     FOR standing_penalty IN
8043         SELECT  DISTINCT csp.*
8044           FROM  actor.usr_standing_penalty usp
8045                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8046           WHERE usr = match_user
8047                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
8048                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8049                 AND csp.block_list LIKE penalty_type LOOP
8050
8051         result.fail_part := standing_penalty.name;
8052         result.success := FALSE;
8053         done := TRUE;
8054         RETURN NEXT result;
8055     END LOOP;
8056
8057     -- Fail if the user has too many items with specific circ_modifiers checked out
8058     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
8059         SELECT  INTO items_out COUNT(*)
8060           FROM  action.circulation circ
8061             JOIN asset.copy cp ON (cp.id = circ.target_copy)
8062           WHERE circ.usr = match_user
8063                AND circ.circ_lib IN ( SELECT * FROM explode_array(context_org_list) )
8064             AND circ.checkin_time IS NULL
8065             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8066             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);
8067         IF items_out >= out_by_circ_mod.items_out THEN
8068             result.fail_part := 'config.circ_matrix_circ_mod_test';
8069             result.success := FALSE;
8070             done := TRUE;
8071             RETURN NEXT result;
8072         END IF;
8073     END LOOP;
8074
8075     -- If we passed everything, return the successful matchpoint id
8076     IF NOT done THEN
8077         RETURN NEXT result;
8078     END IF;
8079
8080     RETURN;
8081 END;
8082 $func$ LANGUAGE plpgsql;
8083
8084 CREATE TABLE config.remote_account (
8085     id          SERIAL  PRIMARY KEY,
8086     label       TEXT    NOT NULL,
8087     host        TEXT    NOT NULL,   -- name or IP, :port optional
8088     username    TEXT,               -- optional, since we could default to $USER
8089     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
8090     account     TEXT,               -- aka profile or FTP "account" command
8091     path        TEXT,               -- aka directory
8092     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8093     last_activity TIMESTAMP WITH TIME ZONE
8094 );
8095
8096 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8097     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8098     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8099         vendcode    TEXT,
8100         vendacct    TEXT
8101
8102 ) INHERITS (config.remote_account);
8103
8104 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8105
8106 CREATE TABLE acq.claim_type (
8107         id             SERIAL           PRIMARY KEY,
8108         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8109                                                  DEFERRABLE INITIALLY DEFERRED,
8110         code           TEXT             NOT NULL,
8111         description    TEXT             NOT NULL,
8112         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8113 );
8114
8115 CREATE TABLE acq.claim (
8116         id             SERIAL           PRIMARY KEY,
8117         type           INT              NOT NULL REFERENCES acq.claim_type
8118                                                  DEFERRABLE INITIALLY DEFERRED,
8119         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8120                                                  DEFERRABLE INITIALLY DEFERRED
8121 );
8122
8123 CREATE TABLE acq.claim_policy (
8124         id              SERIAL       PRIMARY KEY,
8125         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8126                                      DEFERRABLE INITIALLY DEFERRED,
8127         name            TEXT         NOT NULL,
8128         description     TEXT         NOT NULL,
8129         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8130 );
8131
8132 -- Add a san column for EDI. 
8133 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8134
8135 ALTER TABLE acq.provider ADD COLUMN san INT;
8136
8137 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8138
8139 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8140 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8141
8142 ALTER TABLE acq.provider
8143         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8144
8145 ALTER TABLE acq.provider
8146         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8147
8148 ALTER TABLE acq.provider
8149         ADD COLUMN url TEXT;
8150
8151 ALTER TABLE acq.provider
8152         ADD COLUMN email TEXT;
8153
8154 ALTER TABLE acq.provider
8155         ADD COLUMN phone TEXT;
8156
8157 ALTER TABLE acq.provider
8158         ADD COLUMN fax_phone TEXT;
8159
8160 ALTER TABLE acq.provider
8161         ADD COLUMN default_claim_policy INT
8162                 REFERENCES acq.claim_policy
8163                 DEFERRABLE INITIALLY DEFERRED;
8164
8165 ALTER TABLE action.transit_copy
8166 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8167                                                          DEFERRABLE INITIALLY DEFERRED;
8168
8169 DROP SCHEMA IF EXISTS booking CASCADE;
8170
8171 CREATE SCHEMA booking;
8172
8173 CREATE TABLE booking.resource_type (
8174         id             SERIAL          PRIMARY KEY,
8175         name           TEXT            NOT NULL,
8176         fine_interval  INTERVAL,
8177         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
8178         owner          INT             NOT NULL
8179                                        REFERENCES actor.org_unit( id )
8180                                        DEFERRABLE INITIALLY DEFERRED,
8181         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
8182         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
8183     record         BIGINT          REFERENCES biblio.record_entry (id)
8184                                        DEFERRABLE INITIALLY DEFERRED,
8185     max_fine       NUMERIC(8,2),
8186     elbow_room     INTERVAL,
8187     CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
8188 );
8189
8190 CREATE TABLE booking.resource (
8191         id             SERIAL           PRIMARY KEY,
8192         owner          INT              NOT NULL
8193                                         REFERENCES actor.org_unit(id)
8194                                         DEFERRABLE INITIALLY DEFERRED,
8195         type           INT              NOT NULL
8196                                         REFERENCES booking.resource_type(id)
8197                                         DEFERRABLE INITIALLY DEFERRED,
8198         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
8199         barcode        TEXT             NOT NULL,
8200         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
8201         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8202         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8203         CONSTRAINT br_unique UNIQUE (owner, barcode)
8204 );
8205
8206 -- For non-catalog items: hijack barcode for name/description
8207
8208 CREATE TABLE booking.resource_attr (
8209         id              SERIAL          PRIMARY KEY,
8210         owner           INT             NOT NULL
8211                                         REFERENCES actor.org_unit(id)
8212                                         DEFERRABLE INITIALLY DEFERRED,
8213         name            TEXT            NOT NULL,
8214         resource_type   INT             NOT NULL
8215                                         REFERENCES booking.resource_type(id)
8216                                         ON DELETE CASCADE
8217                                         DEFERRABLE INITIALLY DEFERRED,
8218         required        BOOLEAN         NOT NULL DEFAULT FALSE,
8219         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
8220 );
8221
8222 CREATE TABLE booking.resource_attr_value (
8223         id               SERIAL         PRIMARY KEY,
8224         owner            INT            NOT NULL
8225                                         REFERENCES actor.org_unit(id)
8226                                         DEFERRABLE INITIALLY DEFERRED,
8227         attr             INT            NOT NULL
8228                                         REFERENCES booking.resource_attr(id)
8229                                         DEFERRABLE INITIALLY DEFERRED,
8230         valid_value      TEXT           NOT NULL,
8231         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
8232 );
8233
8234 CREATE TABLE booking.resource_attr_map (
8235         id               SERIAL         PRIMARY KEY,
8236         resource         INT            NOT NULL
8237                                         REFERENCES booking.resource(id)
8238                                         ON DELETE CASCADE
8239                                         DEFERRABLE INITIALLY DEFERRED,
8240         resource_attr    INT            NOT NULL
8241                                         REFERENCES booking.resource_attr(id)
8242                                         ON DELETE CASCADE
8243                                         DEFERRABLE INITIALLY DEFERRED,
8244         value            INT            NOT NULL
8245                                         REFERENCES booking.resource_attr_value(id)
8246                                         DEFERRABLE INITIALLY DEFERRED,
8247         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
8248 );
8249
8250 CREATE TABLE booking.reservation (
8251         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
8252         start_time       TIMESTAMPTZ,
8253         end_time         TIMESTAMPTZ,
8254         capture_time     TIMESTAMPTZ,
8255         cancel_time      TIMESTAMPTZ,
8256         pickup_time      TIMESTAMPTZ,
8257         return_time      TIMESTAMPTZ,
8258         booking_interval INTERVAL,
8259         fine_interval    INTERVAL,
8260         fine_amount      DECIMAL(8,2),
8261         target_resource_type  INT       NOT NULL
8262                                         REFERENCES booking.resource_type(id)
8263                                         ON DELETE CASCADE
8264                                         DEFERRABLE INITIALLY DEFERRED,
8265         target_resource  INT            REFERENCES booking.resource(id)
8266                                         ON DELETE CASCADE
8267                                         DEFERRABLE INITIALLY DEFERRED,
8268         current_resource INT            REFERENCES booking.resource(id)
8269                                         ON DELETE CASCADE
8270                                         DEFERRABLE INITIALLY DEFERRED,
8271         request_lib      INT            NOT NULL
8272                                         REFERENCES actor.org_unit(id)
8273                                         DEFERRABLE INITIALLY DEFERRED,
8274         pickup_lib       INT            REFERENCES actor.org_unit(id)
8275                                         DEFERRABLE INITIALLY DEFERRED,
8276         capture_staff    INT            REFERENCES actor.usr(id)
8277                                         DEFERRABLE INITIALLY DEFERRED,
8278     max_fine         NUMERIC(8,2)
8279 ) INHERITS (money.billable_xact);
8280
8281 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
8282
8283 ALTER TABLE booking.reservation
8284         ADD CONSTRAINT booking_reservation_usr_fkey
8285         FOREIGN KEY (usr) REFERENCES actor.usr (id)
8286         DEFERRABLE INITIALLY DEFERRED;
8287
8288 CREATE TABLE booking.reservation_attr_value_map (
8289         id               SERIAL         PRIMARY KEY,
8290         reservation      INT            NOT NULL
8291                                         REFERENCES booking.reservation(id)
8292                                         ON DELETE CASCADE
8293                                         DEFERRABLE INITIALLY DEFERRED,
8294         attr_value       INT            NOT NULL
8295                                         REFERENCES booking.resource_attr_value(id)
8296                                         ON DELETE CASCADE
8297                                         DEFERRABLE INITIALLY DEFERRED,
8298         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
8299 );
8300
8301 -- represents a circ chain summary
8302 CREATE TYPE action.circ_chain_summary AS (
8303     num_circs INTEGER,
8304     start_time TIMESTAMP WITH TIME ZONE,
8305     checkout_workstation TEXT,
8306     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8307     last_stop_fines TEXT,
8308     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8309     last_renewal_workstation TEXT, -- NULL if no renewals
8310     last_checkin_workstation TEXT,
8311     last_checkin_time TIMESTAMP WITH TIME ZONE,
8312     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8313 );
8314
8315 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8316 DECLARE
8317     tmp_circ action.circulation%ROWTYPE;
8318     circ_0 action.circulation%ROWTYPE;
8319 BEGIN
8320
8321     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8322
8323     IF tmp_circ IS NULL THEN
8324         RETURN NEXT tmp_circ;
8325     END IF;
8326     circ_0 := tmp_circ;
8327
8328     -- find the front of the chain
8329     WHILE TRUE LOOP
8330         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8331         IF tmp_circ IS NULL THEN
8332             EXIT;
8333         END IF;
8334         circ_0 := tmp_circ;
8335     END LOOP;
8336
8337     -- now send the circs to the caller, oldest to newest
8338     tmp_circ := circ_0;
8339     WHILE TRUE LOOP
8340         IF tmp_circ IS NULL THEN
8341             EXIT;
8342         END IF;
8343         RETURN NEXT tmp_circ;
8344         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8345     END LOOP;
8346
8347 END;
8348 $$ LANGUAGE 'plpgsql';
8349
8350 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8351
8352 DECLARE
8353
8354     -- first circ in the chain
8355     circ_0 action.circulation%ROWTYPE;
8356
8357     -- last circ in the chain
8358     circ_n action.circulation%ROWTYPE;
8359
8360     -- circ chain under construction
8361     chain action.circ_chain_summary;
8362     tmp_circ action.circulation%ROWTYPE;
8363
8364 BEGIN
8365     
8366     chain.num_circs := 0;
8367     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8368
8369         IF chain.num_circs = 0 THEN
8370             circ_0 := tmp_circ;
8371         END IF;
8372
8373         chain.num_circs := chain.num_circs + 1;
8374         circ_n := tmp_circ;
8375     END LOOP;
8376
8377     chain.start_time := circ_0.xact_start;
8378     chain.last_stop_fines := circ_n.stop_fines;
8379     chain.last_stop_fines_time := circ_n.stop_fines_time;
8380     chain.last_checkin_time := circ_n.checkin_time;
8381     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8382     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8383     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8384
8385     IF chain.num_circs > 1 THEN
8386         chain.last_renewal_time := circ_n.xact_start;
8387         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8388     END IF;
8389
8390     RETURN chain;
8391
8392 END;
8393 $$ LANGUAGE 'plpgsql';
8394
8395 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8396 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8397 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8398
8399 ALTER TABLE config.standing_penalty
8400         ADD COLUMN org_depth   INTEGER;
8401
8402 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8403 DECLARE
8404     user_object         actor.usr%ROWTYPE;
8405     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8406     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8407     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8408     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8409     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8410     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8411     tmp_grp             INT;
8412     items_overdue       INT;
8413     items_out           INT;
8414     context_org_list    INT[];
8415     current_fines        NUMERIC(8,2) := 0.0;
8416     tmp_fines            NUMERIC(8,2);
8417     tmp_groc            RECORD;
8418     tmp_circ            RECORD;
8419     tmp_org             actor.org_unit%ROWTYPE;
8420     tmp_penalty         config.standing_penalty%ROWTYPE;
8421     tmp_depth           INTEGER;
8422 BEGIN
8423     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8424
8425     -- Max fines
8426     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8427
8428     -- Fail if the user has a high fine balance
8429     LOOP
8430         tmp_grp := user_object.profile;
8431         LOOP
8432             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8433
8434             IF max_fines.threshold IS NULL THEN
8435                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8436             ELSE
8437                 EXIT;
8438             END IF;
8439
8440             IF tmp_grp IS NULL THEN
8441                 EXIT;
8442             END IF;
8443         END LOOP;
8444
8445         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8446             EXIT;
8447         END IF;
8448
8449         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8450
8451     END LOOP;
8452
8453     IF max_fines.threshold IS NOT NULL THEN
8454
8455         FOR existing_sp_row IN
8456                 SELECT  *
8457                   FROM  actor.usr_standing_penalty
8458                   WHERE usr = match_user
8459                         AND org_unit = max_fines.org_unit
8460                         AND (stop_date IS NULL or stop_date > NOW())
8461                         AND standing_penalty = 1
8462                 LOOP
8463             RETURN NEXT existing_sp_row;
8464         END LOOP;
8465
8466         SELECT  SUM(f.balance_owed) INTO current_fines
8467           FROM  money.materialized_billable_xact_summary f
8468                 JOIN (
8469                     SELECT  r.id
8470                       FROM  booking.reservation r
8471                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8472                       WHERE usr = match_user
8473                             AND xact_finish IS NULL
8474                                 UNION ALL
8475                     SELECT  g.id
8476                       FROM  money.grocery g
8477                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8478                       WHERE usr = match_user
8479                             AND xact_finish IS NULL
8480                                 UNION ALL
8481                     SELECT  circ.id
8482                       FROM  action.circulation circ
8483                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8484                       WHERE usr = match_user
8485                             AND xact_finish IS NULL ) l USING (id);
8486
8487         IF current_fines >= max_fines.threshold THEN
8488             new_sp_row.usr := match_user;
8489             new_sp_row.org_unit := max_fines.org_unit;
8490             new_sp_row.standing_penalty := 1;
8491             RETURN NEXT new_sp_row;
8492         END IF;
8493     END IF;
8494
8495     -- Start over for max overdue
8496     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8497
8498     -- Fail if the user has too many overdue items
8499     LOOP
8500         tmp_grp := user_object.profile;
8501         LOOP
8502
8503             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8504
8505             IF max_overdue.threshold IS NULL THEN
8506                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8507             ELSE
8508                 EXIT;
8509             END IF;
8510
8511             IF tmp_grp IS NULL THEN
8512                 EXIT;
8513             END IF;
8514         END LOOP;
8515
8516         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8517             EXIT;
8518         END IF;
8519
8520         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8521
8522     END LOOP;
8523
8524     IF max_overdue.threshold IS NOT NULL THEN
8525
8526         FOR existing_sp_row IN
8527                 SELECT  *
8528                   FROM  actor.usr_standing_penalty
8529                   WHERE usr = match_user
8530                         AND org_unit = max_overdue.org_unit
8531                         AND (stop_date IS NULL or stop_date > NOW())
8532                         AND standing_penalty = 2
8533                 LOOP
8534             RETURN NEXT existing_sp_row;
8535         END LOOP;
8536
8537         SELECT  INTO items_overdue COUNT(*)
8538           FROM  action.circulation circ
8539                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8540           WHERE circ.usr = match_user
8541             AND circ.checkin_time IS NULL
8542             AND circ.due_date < NOW()
8543             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8544
8545         IF items_overdue >= max_overdue.threshold::INT THEN
8546             new_sp_row.usr := match_user;
8547             new_sp_row.org_unit := max_overdue.org_unit;
8548             new_sp_row.standing_penalty := 2;
8549             RETURN NEXT new_sp_row;
8550         END IF;
8551     END IF;
8552
8553     -- Start over for max out
8554     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8555
8556     -- Fail if the user has too many checked out items
8557     LOOP
8558         tmp_grp := user_object.profile;
8559         LOOP
8560             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8561
8562             IF max_items_out.threshold IS NULL THEN
8563                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8564             ELSE
8565                 EXIT;
8566             END IF;
8567
8568             IF tmp_grp IS NULL THEN
8569                 EXIT;
8570             END IF;
8571         END LOOP;
8572
8573         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8574             EXIT;
8575         END IF;
8576
8577         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8578
8579     END LOOP;
8580
8581
8582     -- Fail if the user has too many items checked out
8583     IF max_items_out.threshold IS NOT NULL THEN
8584
8585         FOR existing_sp_row IN
8586                 SELECT  *
8587                   FROM  actor.usr_standing_penalty
8588                   WHERE usr = match_user
8589                         AND org_unit = max_items_out.org_unit
8590                         AND (stop_date IS NULL or stop_date > NOW())
8591                         AND standing_penalty = 3
8592                 LOOP
8593             RETURN NEXT existing_sp_row;
8594         END LOOP;
8595
8596         SELECT  INTO items_out COUNT(*)
8597           FROM  action.circulation circ
8598                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8599           WHERE circ.usr = match_user
8600                 AND circ.checkin_time IS NULL
8601                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8602
8603            IF items_out >= max_items_out.threshold::INT THEN
8604             new_sp_row.usr := match_user;
8605             new_sp_row.org_unit := max_items_out.org_unit;
8606             new_sp_row.standing_penalty := 3;
8607             RETURN NEXT new_sp_row;
8608            END IF;
8609     END IF;
8610
8611     -- Start over for collections warning
8612     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8613
8614     -- Fail if the user has a collections-level fine balance
8615     LOOP
8616         tmp_grp := user_object.profile;
8617         LOOP
8618             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8619
8620             IF max_fines.threshold IS NULL THEN
8621                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8622             ELSE
8623                 EXIT;
8624             END IF;
8625
8626             IF tmp_grp IS NULL THEN
8627                 EXIT;
8628             END IF;
8629         END LOOP;
8630
8631         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8632             EXIT;
8633         END IF;
8634
8635         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8636
8637     END LOOP;
8638
8639     IF max_fines.threshold IS NOT NULL THEN
8640
8641         FOR existing_sp_row IN
8642                 SELECT  *
8643                   FROM  actor.usr_standing_penalty
8644                   WHERE usr = match_user
8645                         AND org_unit = max_fines.org_unit
8646                         AND (stop_date IS NULL or stop_date > NOW())
8647                         AND standing_penalty = 4
8648                 LOOP
8649             RETURN NEXT existing_sp_row;
8650         END LOOP;
8651
8652         SELECT  SUM(f.balance_owed) INTO current_fines
8653           FROM  money.materialized_billable_xact_summary f
8654                 JOIN (
8655                     SELECT  r.id
8656                       FROM  booking.reservation r
8657                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8658                       WHERE usr = match_user
8659                             AND xact_finish IS NULL
8660                                 UNION ALL
8661                     SELECT  g.id
8662                       FROM  money.grocery g
8663                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8664                       WHERE usr = match_user
8665                             AND xact_finish IS NULL
8666                                 UNION ALL
8667                     SELECT  circ.id
8668                       FROM  action.circulation circ
8669                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8670                       WHERE usr = match_user
8671                             AND xact_finish IS NULL ) l USING (id);
8672
8673         IF current_fines >= max_fines.threshold THEN
8674             new_sp_row.usr := match_user;
8675             new_sp_row.org_unit := max_fines.org_unit;
8676             new_sp_row.standing_penalty := 4;
8677             RETURN NEXT new_sp_row;
8678         END IF;
8679     END IF;
8680
8681     -- Start over for in collections
8682     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8683
8684     -- Remove the in-collections penalty if the user has paid down enough
8685     -- This penalty is different, because this code is not responsible for creating 
8686     -- new in-collections penalties, only for removing them
8687     LOOP
8688         tmp_grp := user_object.profile;
8689         LOOP
8690             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8691
8692             IF max_fines.threshold IS NULL THEN
8693                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8694             ELSE
8695                 EXIT;
8696             END IF;
8697
8698             IF tmp_grp IS NULL THEN
8699                 EXIT;
8700             END IF;
8701         END LOOP;
8702
8703         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8704             EXIT;
8705         END IF;
8706
8707         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8708
8709     END LOOP;
8710
8711     IF max_fines.threshold IS NOT NULL THEN
8712
8713         -- first, see if the user had paid down to the threshold
8714         SELECT  SUM(f.balance_owed) INTO current_fines
8715           FROM  money.materialized_billable_xact_summary f
8716                 JOIN (
8717                     SELECT  r.id
8718                       FROM  booking.reservation r
8719                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8720                       WHERE usr = match_user
8721                             AND xact_finish IS NULL
8722                                 UNION ALL
8723                     SELECT  g.id
8724                       FROM  money.grocery g
8725                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8726                       WHERE usr = match_user
8727                             AND xact_finish IS NULL
8728                                 UNION ALL
8729                     SELECT  circ.id
8730                       FROM  action.circulation circ
8731                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8732                       WHERE usr = match_user
8733                             AND xact_finish IS NULL ) l USING (id);
8734
8735         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8736             -- patron has paid down enough
8737
8738             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8739
8740             IF tmp_penalty.org_depth IS NOT NULL THEN
8741
8742                 -- since this code is not responsible for applying the penalty, it can't 
8743                 -- guarantee the current context org will match the org at which the penalty 
8744                 --- was applied.  search up the org tree until we hit the configured penalty depth
8745                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8746                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8747
8748                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8749
8750                     FOR existing_sp_row IN
8751                             SELECT  *
8752                             FROM  actor.usr_standing_penalty
8753                             WHERE usr = match_user
8754                                     AND org_unit = tmp_org.id
8755                                     AND (stop_date IS NULL or stop_date > NOW())
8756                                     AND standing_penalty = 30 
8757                             LOOP
8758
8759                         -- Penalty exists, return it for removal
8760                         RETURN NEXT existing_sp_row;
8761                     END LOOP;
8762
8763                     IF tmp_org.parent_ou IS NULL THEN
8764                         EXIT;
8765                     END IF;
8766
8767                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8768                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8769                 END LOOP;
8770
8771             ELSE
8772
8773                 -- no penalty depth is defined, look for exact matches
8774
8775                 FOR existing_sp_row IN
8776                         SELECT  *
8777                         FROM  actor.usr_standing_penalty
8778                         WHERE usr = match_user
8779                                 AND org_unit = max_fines.org_unit
8780                                 AND (stop_date IS NULL or stop_date > NOW())
8781                                 AND standing_penalty = 30 
8782                         LOOP
8783                     -- Penalty exists, return it for removal
8784                     RETURN NEXT existing_sp_row;
8785                 END LOOP;
8786             END IF;
8787     
8788         END IF;
8789
8790     END IF;
8791
8792     RETURN;
8793 END;
8794 $func$ LANGUAGE plpgsql;
8795
8796 -- Create a default row in acq.fiscal_calendar
8797 -- Add a column in actor.org_unit to point to it
8798
8799 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8800
8801 ALTER TABLE actor.org_unit
8802 ADD COLUMN fiscal_calendar INT NOT NULL
8803         REFERENCES acq.fiscal_calendar( id )
8804         DEFERRABLE INITIALLY DEFERRED
8805         DEFAULT 1;
8806
8807 ALTER TABLE auditor.actor_org_unit_history
8808         ADD COLUMN fiscal_calendar INT;
8809
8810 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8811
8812 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8813
8814 ALTER TABLE acq.funding_source_credit
8815 ADD COLUMN deadline_date TIMESTAMPTZ;
8816
8817 ALTER TABLE acq.funding_source_credit
8818 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8819
8820 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8821
8822 CREATE TABLE acq.fund_transfer (
8823         id               SERIAL         PRIMARY KEY,
8824         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8825                                         DEFERRABLE INITIALLY DEFERRED,
8826         src_amount       NUMERIC        NOT NULL,
8827         dest_fund        INT            REFERENCES acq.fund( id )
8828                                         DEFERRABLE INITIALLY DEFERRED,
8829         dest_amount      NUMERIC,
8830         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
8831         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
8832                                         DEFERRABLE INITIALLY DEFERRED,
8833         note             TEXT,
8834     funding_source_credit INTEGER   NOT NULL
8835                                         REFERENCES acq.funding_source_credit(id)
8836                                         DEFERRABLE INITIALLY DEFERRED
8837 );
8838
8839 CREATE INDEX acqftr_usr_idx
8840 ON acq.fund_transfer( transfer_user );
8841
8842 COMMENT ON TABLE acq.fund_transfer IS $$
8843 /*
8844  * Copyright (C) 2009  Georgia Public Library Service
8845  * Scott McKellar <scott@esilibrary.com>
8846  *
8847  * Fund Transfer
8848  *
8849  * Each row represents the transfer of money from a source fund
8850  * to a destination fund.  There should be corresponding entries
8851  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
8852  * to record how much money moved from which fund to which other
8853  * fund.
8854  * 
8855  * The presence of two amount fields, rather than one, reflects
8856  * the possibility that the two funds are denominated in different
8857  * currencies.  If they use the same currency type, the two
8858  * amounts should be the same.
8859  *
8860  * ****
8861  *
8862  * This program is free software; you can redistribute it and/or
8863  * modify it under the terms of the GNU General Public License
8864  * as published by the Free Software Foundation; either version 2
8865  * of the License, or (at your option) any later version.
8866  *
8867  * This program is distributed in the hope that it will be useful,
8868  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8869  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
8870  * GNU General Public License for more details.
8871  */
8872 $$;
8873
8874 CREATE TABLE acq.claim_event_type (
8875         id             SERIAL           PRIMARY KEY,
8876         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8877                                                  DEFERRABLE INITIALLY DEFERRED,
8878         code           TEXT             NOT NULL,
8879         description    TEXT             NOT NULL,
8880         library_initiated BOOL          NOT NULL DEFAULT FALSE,
8881         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
8882 );
8883
8884 CREATE TABLE acq.claim_event (
8885         id             BIGSERIAL        PRIMARY KEY,
8886         type           INT              NOT NULL REFERENCES acq.claim_event_type
8887                                                  DEFERRABLE INITIALLY DEFERRED,
8888         claim          SERIAL           NOT NULL REFERENCES acq.claim
8889                                                  DEFERRABLE INITIALLY DEFERRED,
8890         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
8891         creator        INT              NOT NULL REFERENCES actor.usr
8892                                                  DEFERRABLE INITIALLY DEFERRED,
8893         note           TEXT
8894 );
8895
8896 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
8897
8898 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
8899         src_usr  IN INTEGER,
8900         dest_usr IN INTEGER
8901 ) RETURNS VOID AS $$
8902 DECLARE
8903         suffix TEXT;
8904         renamable_row RECORD;
8905 BEGIN
8906
8907         UPDATE actor.usr SET
8908                 active = FALSE,
8909                 card = NULL,
8910                 mailing_address = NULL,
8911                 billing_address = NULL
8912         WHERE id = src_usr;
8913
8914         -- acq.*
8915         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
8916         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
8917         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
8918         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
8919         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
8920         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
8921         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
8922
8923         -- Update with a rename to avoid collisions
8924         FOR renamable_row in
8925                 SELECT id, name
8926                 FROM   acq.picklist
8927                 WHERE  owner = src_usr
8928         LOOP
8929                 suffix := ' (' || src_usr || ')';
8930                 LOOP
8931                         BEGIN
8932                                 UPDATE  acq.picklist
8933                                 SET     owner = dest_usr, name = name || suffix
8934                                 WHERE   id = renamable_row.id;
8935                         EXCEPTION WHEN unique_violation THEN
8936                                 suffix := suffix || ' ';
8937                                 CONTINUE;
8938                         END;
8939                         EXIT;
8940                 END LOOP;
8941         END LOOP;
8942
8943         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
8944         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
8945         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
8946         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
8947         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
8948         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
8949         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
8950         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
8951
8952         -- action.*
8953         DELETE FROM action.circulation WHERE usr = src_usr;
8954         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
8955         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
8956         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
8957         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
8958         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
8959         DELETE FROM action.hold_request WHERE usr = src_usr;
8960         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
8961         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
8962         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
8963         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
8964         DELETE FROM action.survey_response WHERE usr = src_usr;
8965         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
8966
8967         -- actor.*
8968         DELETE FROM actor.card WHERE usr = src_usr;
8969         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
8970
8971         -- The following update is intended to avoid transient violations of a foreign
8972         -- key constraint, whereby actor.usr_address references itself.  It may not be
8973         -- necessary, but it does no harm.
8974         UPDATE actor.usr_address SET replaces = NULL
8975                 WHERE usr = src_usr AND replaces IS NOT NULL;
8976         DELETE FROM actor.usr_address WHERE usr = src_usr;
8977         DELETE FROM actor.usr_note WHERE usr = src_usr;
8978         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
8979         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
8980         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
8981         DELETE FROM actor.usr_setting WHERE usr = src_usr;
8982         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
8983         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
8984
8985         -- asset.*
8986         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
8987         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
8988         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
8989         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
8990         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
8991         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
8992
8993         -- auditor.*
8994         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
8995         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
8996         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
8997         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
8998         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
8999         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
9000         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
9001         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
9002
9003         -- biblio.*
9004         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
9005         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
9006         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
9007         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
9008
9009         -- container.*
9010         -- Update buckets with a rename to avoid collisions
9011         FOR renamable_row in
9012                 SELECT id, name
9013                 FROM   container.biblio_record_entry_bucket
9014                 WHERE  owner = src_usr
9015         LOOP
9016                 suffix := ' (' || src_usr || ')';
9017                 LOOP
9018                         BEGIN
9019                                 UPDATE  container.biblio_record_entry_bucket
9020                                 SET     owner = dest_usr, name = name || suffix
9021                                 WHERE   id = renamable_row.id;
9022                         EXCEPTION WHEN unique_violation THEN
9023                                 suffix := suffix || ' ';
9024                                 CONTINUE;
9025                         END;
9026                         EXIT;
9027                 END LOOP;
9028         END LOOP;
9029
9030         FOR renamable_row in
9031                 SELECT id, name
9032                 FROM   container.call_number_bucket
9033                 WHERE  owner = src_usr
9034         LOOP
9035                 suffix := ' (' || src_usr || ')';
9036                 LOOP
9037                         BEGIN
9038                                 UPDATE  container.call_number_bucket
9039                                 SET     owner = dest_usr, name = name || suffix
9040                                 WHERE   id = renamable_row.id;
9041                         EXCEPTION WHEN unique_violation THEN
9042                                 suffix := suffix || ' ';
9043                                 CONTINUE;
9044                         END;
9045                         EXIT;
9046                 END LOOP;
9047         END LOOP;
9048
9049         FOR renamable_row in
9050                 SELECT id, name
9051                 FROM   container.copy_bucket
9052                 WHERE  owner = src_usr
9053         LOOP
9054                 suffix := ' (' || src_usr || ')';
9055                 LOOP
9056                         BEGIN
9057                                 UPDATE  container.copy_bucket
9058                                 SET     owner = dest_usr, name = name || suffix
9059                                 WHERE   id = renamable_row.id;
9060                         EXCEPTION WHEN unique_violation THEN
9061                                 suffix := suffix || ' ';
9062                                 CONTINUE;
9063                         END;
9064                         EXIT;
9065                 END LOOP;
9066         END LOOP;
9067
9068         FOR renamable_row in
9069                 SELECT id, name
9070                 FROM   container.user_bucket
9071                 WHERE  owner = src_usr
9072         LOOP
9073                 suffix := ' (' || src_usr || ')';
9074                 LOOP
9075                         BEGIN
9076                                 UPDATE  container.user_bucket
9077                                 SET     owner = dest_usr, name = name || suffix
9078                                 WHERE   id = renamable_row.id;
9079                         EXCEPTION WHEN unique_violation THEN
9080                                 suffix := suffix || ' ';
9081                                 CONTINUE;
9082                         END;
9083                         EXIT;
9084                 END LOOP;
9085         END LOOP;
9086
9087         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
9088
9089         -- money.*
9090         DELETE FROM money.billable_xact WHERE usr = src_usr;
9091         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9092         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9093
9094         -- permission.*
9095         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9096         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9097         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9098         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9099
9100         -- reporter.*
9101         -- Update with a rename to avoid collisions
9102         BEGIN
9103                 FOR renamable_row in
9104                         SELECT id, name
9105                         FROM   reporter.output_folder
9106                         WHERE  owner = src_usr
9107                 LOOP
9108                         suffix := ' (' || src_usr || ')';
9109                         LOOP
9110                                 BEGIN
9111                                         UPDATE  reporter.output_folder
9112                                         SET     owner = dest_usr, name = name || suffix
9113                                         WHERE   id = renamable_row.id;
9114                                 EXCEPTION WHEN unique_violation THEN
9115                                         suffix := suffix || ' ';
9116                                         CONTINUE;
9117                                 END;
9118                                 EXIT;
9119                         END LOOP;
9120                 END LOOP;
9121         EXCEPTION WHEN undefined_table THEN
9122                 -- do nothing
9123         END;
9124
9125         BEGIN
9126                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9127         EXCEPTION WHEN undefined_table THEN
9128                 -- do nothing
9129         END;
9130
9131         -- Update with a rename to avoid collisions
9132         BEGIN
9133                 FOR renamable_row in
9134                         SELECT id, name
9135                         FROM   reporter.report_folder
9136                         WHERE  owner = src_usr
9137                 LOOP
9138                         suffix := ' (' || src_usr || ')';
9139                         LOOP
9140                                 BEGIN
9141                                         UPDATE  reporter.report_folder
9142                                         SET     owner = dest_usr, name = name || suffix
9143                                         WHERE   id = renamable_row.id;
9144                                 EXCEPTION WHEN unique_violation THEN
9145                                         suffix := suffix || ' ';
9146                                         CONTINUE;
9147                                 END;
9148                                 EXIT;
9149                         END LOOP;
9150                 END LOOP;
9151         EXCEPTION WHEN undefined_table THEN
9152                 -- do nothing
9153         END;
9154
9155         BEGIN
9156                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9157         EXCEPTION WHEN undefined_table THEN
9158                 -- do nothing
9159         END;
9160
9161         BEGIN
9162                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9163         EXCEPTION WHEN undefined_table THEN
9164                 -- do nothing
9165         END;
9166
9167         -- Update with a rename to avoid collisions
9168         BEGIN
9169                 FOR renamable_row in
9170                         SELECT id, name
9171                         FROM   reporter.template_folder
9172                         WHERE  owner = src_usr
9173                 LOOP
9174                         suffix := ' (' || src_usr || ')';
9175                         LOOP
9176                                 BEGIN
9177                                         UPDATE  reporter.template_folder
9178                                         SET     owner = dest_usr, name = name || suffix
9179                                         WHERE   id = renamable_row.id;
9180                                 EXCEPTION WHEN unique_violation THEN
9181                                         suffix := suffix || ' ';
9182                                         CONTINUE;
9183                                 END;
9184                                 EXIT;
9185                         END LOOP;
9186                 END LOOP;
9187         EXCEPTION WHEN undefined_table THEN
9188         -- do nothing
9189         END;
9190
9191         -- vandelay.*
9192         -- Update with a rename to avoid collisions
9193         FOR renamable_row in
9194                 SELECT id, name
9195                 FROM   vandelay.queue
9196                 WHERE  owner = src_usr
9197         LOOP
9198                 suffix := ' (' || src_usr || ')';
9199                 LOOP
9200                         BEGIN
9201                                 UPDATE  vandelay.queue
9202                                 SET     owner = dest_usr, name = name || suffix
9203                                 WHERE   id = renamable_row.id;
9204                         EXCEPTION WHEN unique_violation THEN
9205                                 suffix := suffix || ' ';
9206                                 CONTINUE;
9207                         END;
9208                         EXIT;
9209                 END LOOP;
9210         END LOOP;
9211
9212 END;
9213 $$ LANGUAGE plpgsql;
9214
9215 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9216 /**
9217  * Finds rows dependent on a given row in actor.usr and either deletes them
9218  * or reassigns them to a different user.
9219  */
9220 $$;
9221
9222 CREATE OR REPLACE FUNCTION actor.usr_delete(
9223         src_usr  IN INTEGER,
9224         dest_usr IN INTEGER
9225 ) RETURNS VOID AS $$
9226 DECLARE
9227         old_profile actor.usr.profile%type;
9228         old_home_ou actor.usr.home_ou%type;
9229         new_profile actor.usr.profile%type;
9230         new_home_ou actor.usr.home_ou%type;
9231         new_name    text;
9232         new_dob     actor.usr.dob%type;
9233 BEGIN
9234         SELECT
9235                 id || '-PURGED-' || now(),
9236                 profile,
9237                 home_ou,
9238                 dob
9239         INTO
9240                 new_name,
9241                 old_profile,
9242                 old_home_ou,
9243                 new_dob
9244         FROM
9245                 actor.usr
9246         WHERE
9247                 id = src_usr;
9248         --
9249         -- Quit if no such user
9250         --
9251         IF old_profile IS NULL THEN
9252                 RETURN;
9253         END IF;
9254         --
9255         perform actor.usr_purge_data( src_usr, dest_usr );
9256         --
9257         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9258         -- could assume that there is only one root.  Theoretically, someday, maybe,
9259         -- there could be multiple roots, so we take extra trouble to get the right ones.
9260         --
9261         SELECT
9262                 id
9263         INTO
9264                 new_profile
9265         FROM
9266                 permission.grp_ancestors( old_profile )
9267         WHERE
9268                 parent is null;
9269         --
9270         SELECT
9271                 id
9272         INTO
9273                 new_home_ou
9274         FROM
9275                 actor.org_unit_ancestors( old_home_ou )
9276         WHERE
9277                 parent_ou is null;
9278         --
9279         -- Truncate date of birth
9280         --
9281         IF new_dob IS NOT NULL THEN
9282                 new_dob := date_trunc( 'year', new_dob );
9283         END IF;
9284         --
9285         UPDATE
9286                 actor.usr
9287                 SET
9288                         card = NULL,
9289                         profile = new_profile,
9290                         usrname = new_name,
9291                         email = NULL,
9292                         passwd = random()::text,
9293                         standing = DEFAULT,
9294                         ident_type = 
9295                         (
9296                                 SELECT MIN( id )
9297                                 FROM config.identification_type
9298                         ),
9299                         ident_value = NULL,
9300                         ident_type2 = NULL,
9301                         ident_value2 = NULL,
9302                         net_access_level = DEFAULT,
9303                         photo_url = NULL,
9304                         prefix = NULL,
9305                         first_given_name = new_name,
9306                         second_given_name = NULL,
9307                         family_name = new_name,
9308                         suffix = NULL,
9309                         alias = NULL,
9310                         day_phone = NULL,
9311                         evening_phone = NULL,
9312                         other_phone = NULL,
9313                         mailing_address = NULL,
9314                         billing_address = NULL,
9315                         home_ou = new_home_ou,
9316                         dob = new_dob,
9317                         active = FALSE,
9318                         master_account = DEFAULT, 
9319                         super_user = DEFAULT,
9320                         barred = FALSE,
9321                         deleted = TRUE,
9322                         juvenile = DEFAULT,
9323                         usrgroup = 0,
9324                         claims_returned_count = DEFAULT,
9325                         credit_forward_balance = DEFAULT,
9326                         last_xact_id = DEFAULT,
9327                         alert_message = NULL,
9328                         create_date = now(),
9329                         expire_date = now()
9330         WHERE
9331                 id = src_usr;
9332 END;
9333 $$ LANGUAGE plpgsql;
9334
9335 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9336 /**
9337  * Logically deletes a user.  Removes personally identifiable information,
9338  * and purges associated data in other tables.
9339  */
9340 $$;
9341
9342 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9343
9344 ALTER TABLE acq.fund
9345 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9346
9347 ALTER TABLE acq.fund
9348         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9349
9350 -- A fund can't roll over if it doesn't propagate from one year to the next
9351
9352 ALTER TABLE acq.fund
9353         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9354         ( propagate OR NOT rollover );
9355
9356 ALTER TABLE acq.fund
9357         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9358
9359 ALTER TABLE acq.fund
9360     ADD COLUMN balance_warning_percent INT
9361     CONSTRAINT balance_warning_percent_limit
9362         CHECK( balance_warning_percent <= 100 );
9363
9364 ALTER TABLE acq.fund
9365     ADD COLUMN balance_stop_percent INT
9366     CONSTRAINT balance_stop_percent_limit
9367         CHECK( balance_stop_percent <= 100 );
9368
9369 CREATE VIEW acq.ordered_funding_source_credit AS
9370         SELECT
9371                 CASE WHEN deadline_date IS NULL THEN
9372                         2
9373                 ELSE
9374                         1
9375                 END AS sort_priority,
9376                 CASE WHEN deadline_date IS NULL THEN
9377                         effective_date
9378                 ELSE
9379                         deadline_date
9380                 END AS sort_date,
9381                 id,
9382                 funding_source,
9383                 amount,
9384                 note
9385         FROM
9386                 acq.funding_source_credit;
9387
9388 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9389 /*
9390  * Copyright (C) 2009  Georgia Public Library Service
9391  * Scott McKellar <scott@gmail.com>
9392  *
9393  * The acq.ordered_funding_source_credit view is a prioritized
9394  * ordering of funding source credits.  When ordered by the first
9395  * three columns, this view defines the order in which the various
9396  * credits are to be tapped for spending, subject to the allocations
9397  * in the acq.fund_allocation table.
9398  *
9399  * The first column reflects the principle that we should spend
9400  * money with deadlines before spending money without deadlines.
9401  *
9402  * The second column reflects the principle that we should spend the
9403  * oldest money first.  For money with deadlines, that means that we
9404  * spend first from the credit with the earliest deadline.  For
9405  * money without deadlines, we spend first from the credit with the
9406  * earliest effective date.  
9407  *
9408  * The third column is a tie breaker to ensure a consistent
9409  * ordering.
9410  *
9411  * ****
9412  *
9413  * This program is free software; you can redistribute it and/or
9414  * modify it under the terms of the GNU General Public License
9415  * as published by the Free Software Foundation; either version 2
9416  * of the License, or (at your option) any later version.
9417  *
9418  * This program is distributed in the hope that it will be useful,
9419  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9420  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9421  * GNU General Public License for more details.
9422  */
9423 $$;
9424
9425 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9426     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9427       FROM  money.materialized_billable_xact_summary m
9428             LEFT JOIN action.circulation c ON (c.id = m.id)
9429             LEFT JOIN money.grocery g ON (g.id = m.id)
9430             LEFT JOIN booking.reservation r ON (r.id = m.id);
9431
9432 CREATE TABLE config.marc21_rec_type_map (
9433     code        TEXT    PRIMARY KEY,
9434     type_val    TEXT    NOT NULL,
9435     blvl_val    TEXT    NOT NULL
9436 );
9437
9438 CREATE TABLE config.marc21_ff_pos_map (
9439     id          SERIAL  PRIMARY KEY,
9440     fixed_field TEXT    NOT NULL,
9441     tag         TEXT    NOT NULL,
9442     rec_type    TEXT    NOT NULL,
9443     start_pos   INT     NOT NULL,
9444     length      INT     NOT NULL,
9445     default_val TEXT    NOT NULL DEFAULT ' '
9446 );
9447
9448 CREATE TABLE config.marc21_physical_characteristic_type_map (
9449     ptype_key   TEXT    PRIMARY KEY,
9450     label       TEXT    NOT NULL -- I18N
9451 );
9452
9453 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9454     id          SERIAL  PRIMARY KEY,
9455     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9456     subfield    TEXT    NOT NULL,
9457     start_pos   INT     NOT NULL,
9458     length      INT     NOT NULL,
9459     label       TEXT    NOT NULL -- I18N
9460 );
9461
9462 CREATE TABLE config.marc21_physical_characteristic_value_map (
9463     id              SERIAL  PRIMARY KEY,
9464     value           TEXT    NOT NULL,
9465     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9466     label           TEXT    NOT NULL -- I18N
9467 );
9468
9469 ----------------------------------
9470 -- MARC21 record structure data --
9471 ----------------------------------
9472
9473 -- Record type map
9474 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9475 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9476 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9477 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9478 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9479 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9480 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9481 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9482
9483 ------ Physical Characteristics
9484
9485 -- Map
9486 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9487 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9488 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9489 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9490 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9491 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9492 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9493 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');
9494 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9495 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9496 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9497 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9498 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9499 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');
9500 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9501 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9502 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9503 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9504 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9505 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9506 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9507 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9508 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9509 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9510 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');
9511 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');
9512 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');
9513 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');
9514 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9515 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');
9516 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9517 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9518 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
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 ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9521 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9522 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
9523 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');
9524 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9525 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');
9526 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
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_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9529 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9530 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9531 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9532 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9533 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');
9534
9535 -- Electronic Resource
9536 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9537 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9538 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');
9539 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');
9540 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');
9541 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');
9542 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');
9543 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');
9544 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');
9545 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');
9546 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9547 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9548 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9549 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
9550 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');
9551 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');
9552 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9553 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');
9554 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9555 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');
9556 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9557 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9558 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9559 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.');
9560 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.');
9561 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.');
9562 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.');
9563 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.');
9564 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');
9565 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.');
9566 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9567 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.');
9568 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9569 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
9570 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)');
9571 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
9572 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9573 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9574 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9575 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9576 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');
9577 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9578 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');
9579 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');
9580 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9581 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9582 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9583 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9584 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
9585 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9586 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
9587 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');
9588 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');
9589 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');
9590 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)');
9591 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9592 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');
9593 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9594 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9595 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9596 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9597 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9598 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9599 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9600 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
9601 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9602 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');
9603 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9604 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9605 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9606
9607 -- Globe
9608 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9609 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9610 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');
9611 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');
9612 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');
9613 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');
9614 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9615 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9616 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
9617 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');
9618 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9619 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9620 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9621 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9622 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9623 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9624 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9625 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9626 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9627 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9628 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9629 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9630 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9631 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9632 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');
9633 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9634 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9635
9636 -- Tactile Material
9637 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9638 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9639 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9640 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9641 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9642 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');
9643 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9644 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9645 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9646 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');
9647 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');
9648 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');
9649 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');
9650 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');
9651 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');
9652 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');
9653 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9654 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9655 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9656 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9657 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9658 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9659 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');
9660 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9661 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9662 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9663 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');
9664 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');
9665 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');
9666 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9667 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');
9668 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');
9669 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');
9670 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');
9671 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');
9672 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');
9673 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9674 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');
9675 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');
9676 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9677 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9678 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
9679 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');
9680 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');
9681 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');
9682 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9683 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9684
9685 -- Projected Graphic
9686 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9687 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9688 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');
9689 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9690 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');
9691 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');
9692 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9693 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
9694 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9695 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9696 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');
9697 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9698 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');
9699 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9700 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');
9701 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9702 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9703 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9704 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9705 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9706 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');
9707 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');
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 ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9710 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9711 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9712 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','f','5','1','Sound on medium or separate');
9713 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');
9714 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');
9715 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9716 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9717 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');
9718 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');
9719 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');
9720 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');
9721 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');
9722 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');
9723 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');
9724 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9725 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9726 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9727 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9728 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9729 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Standard 8 mm.');
9730 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.');
9731 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.');
9732 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.');
9733 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.');
9734 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.');
9735 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.');
9736 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.)');
9737 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.)');
9738 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.)');
9739 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.)');
9740 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9741 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.)');
9742 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.)');
9743 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.)');
9744 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.)');
9745 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9746 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9747 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9748 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9749 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9750 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9751 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');
9752 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');
9753 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');
9754 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9755 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9756
9757 -- Microform
9758 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9759 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9760 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');
9761 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');
9762 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');
9763 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');
9764 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9765 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');
9766 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9767 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9768 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9769 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9770 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9771 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9772 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9773 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9774 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
9775 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.');
9776 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.');
9777 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.');
9778 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9779 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.');
9780 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.)');
9781 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.)');
9782 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.)');
9783 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.)');
9784 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9785 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9786 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','f','5','4','Reduction ratio range/Reduction ratio');
9787 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)');
9788 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)');
9789 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)');
9790 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)');
9791 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-)');
9792 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9793 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Reduction ratio varies');
9794 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9795 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');
9796 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9797 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9798 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9799 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9800 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9801 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');
9802 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9803 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9804 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9805 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');
9806 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9807 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9808 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
9809 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');
9810 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');
9811 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');
9812 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');
9813 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9814 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9815 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');
9816 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');
9817 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');
9818 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');
9819 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');
9820 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');
9821 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');
9822 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');
9823 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');
9824 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9825 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9826
9827 -- Non-projected Graphic
9828 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9829 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
9830 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
9831 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
9832 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
9833 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');
9834 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
9835 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
9836 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
9837 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
9838 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');
9839 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
9840 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');
9841 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9842 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9843 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
9844 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');
9845 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');
9846 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9847 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');
9848 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9849 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
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 ('k','e','4','1','Primary support material');
9852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9853 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');
9854 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');
9855 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9856 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9857 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9858 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9859 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9860 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');
9861 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9862 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9863 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9864 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9865 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9866 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9867 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9868 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9869 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
9870 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9871 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');
9872 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');
9873 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9874 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9875 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9876 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9877 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9878 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9879 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9880 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9881 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9882 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9883 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9884 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9885 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9886 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9887
9888 -- Motion Picture
9889 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
9890 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
9891 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');
9892 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');
9893 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');
9894 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9895 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9896 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
9897 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');
9898 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9899 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');
9900 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9901 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9902 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9903 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
9904 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');
9905 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)');
9906 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
9907 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)');
9908 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');
9909 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');
9910 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9911 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9912 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');
9913 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');
9914 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');
9915 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9916 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
9917 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');
9918 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');
9919 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');
9920 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');
9921 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');
9922 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');
9923 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');
9924 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9925 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9926 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9927 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9928 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
9929 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.');
9930 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.');
9931 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.');
9932 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.');
9933 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.');
9934 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.');
9935 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.');
9936 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9937 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9938 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
9939 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9940 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
9941 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');
9942 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');
9943 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
9944 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9945 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9946 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
9947 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');
9948 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
9949 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
9950 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
9951 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');
9952 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');
9953 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');
9954 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');
9955 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9956
9957 -- Remote-sensing Image
9958 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
9959 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
9960 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9961 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
9962 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
9963 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
9964 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
9965 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');
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','e','4','1','Attitude of sensor');
9969 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');
9970 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');
9971 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
9972 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');
9973 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9974 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
9975 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%');
9976 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%');
9977 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%');
9978 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%');
9979 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%');
9980 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%');
9981 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%');
9982 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%');
9983 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%');
9984 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%');
9985 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');
9986 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9987 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
9988 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
9989 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');
9990 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');
9991 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');
9992 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');
9993 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');
9994 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');
9995 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');
9996 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');
9997 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');
9998 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9999 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10000 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
10001 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
10002 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');
10003 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');
10004 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');
10005 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');
10006 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10007 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10008 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
10009 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
10010 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
10011 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10012 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10013 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
10014 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');
10015 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');
10016 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');
10017 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');
10018 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');
10019 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)');
10020 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');
10021 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
10022 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');
10023 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)');
10024 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)');
10025 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)');
10026 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');
10027 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');
10028 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');
10029 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');
10030 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');
10031 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');
10032 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');
10033 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');
10034 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');
10035 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');
10036 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');
10037 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');
10038 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');
10039 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');
10040 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');
10041 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');
10042 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');
10043 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');
10044 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');
10045 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');
10046 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');
10047 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)');
10048 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');
10049 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
10050 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
10051 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');
10052 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');
10053 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10054 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10055
10056 -- Sound Recording
10057 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
10058 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
10059 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');
10060 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
10061 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');
10062 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');
10063 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
10064 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');
10065 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');
10066 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10067 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');
10068 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10069 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
10070 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');
10071 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');
10072 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');
10073 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');
10074 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');
10075 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');
10076 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');
10077 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');
10078 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');
10079 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');
10080 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');
10081 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');
10082 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');
10083 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');
10084 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10085 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10086 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
10087 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10088 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
10089 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10090 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10091 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10092 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10093 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');
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 ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Coarse/standard');
10096 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10097 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10098 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10099 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3 in.');
10100 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.');
10101 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.');
10102 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.');
10103 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.');
10104 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.');
10105 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.)');
10106 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.');
10107 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');
10108 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.');
10109 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.');
10110 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10111 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10112 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
10113 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.');
10114 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.');
10115 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');
10116 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.');
10117 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.');
10118 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10119 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10120 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
10121 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');
10122 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');
10123 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');
10124 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');
10125 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');
10126 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');
10127 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');
10128 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10129 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10130 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10131 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');
10132 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');
10133 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');
10134 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');
10135 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');
10136 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');
10137 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');
10138 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');
10139 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');
10140 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10141 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10142 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
10143 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');
10144 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');
10145 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');
10146 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');
10147 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10148 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10149
10150 -- Videorecording
10151 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10152 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10153 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10154 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10155 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10156 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10157 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10158 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10159 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10160 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');
10161 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10162 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10163 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');
10164 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10165 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10166 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10167 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10168 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10169 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');
10170 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10171 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');
10172 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10173 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10174 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10175 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10176 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');
10177 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');
10178 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');
10179 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');
10180 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.');
10181 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.');
10182 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10183 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10184 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10185 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');
10186 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');
10187 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');
10188 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10189 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10190 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');
10191 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');
10192 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');
10193 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');
10194 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');
10195 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');
10196 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');
10197 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10198 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10199 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10200 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10201 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10202 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.');
10203 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.');
10204 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.');
10205 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.');
10206 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.');
10207 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.');
10208 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10209 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10210 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10211 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10212 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10213 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');
10214 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');
10215 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10216 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10217 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10218
10219 -- Fixed Field position data -- 0-based!
10220 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10221 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10222 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10223 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10224 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10225 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10226 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10227 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10228 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10229 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10230 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10231 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10232 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10233 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10234 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10235 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10236 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10237 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10238 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10239 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10240 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10241 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10242 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10243 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10244 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10245 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10246 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10247 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10248 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10249 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10250 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10251 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10252 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10253 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10254 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10255 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10256 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10257 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10258 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10259 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10260 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10261 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10262 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10263 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10264 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10265 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10266 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10267 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10268 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10269 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10270 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10271 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10272 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10273 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10274 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10275 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10276 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10277 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10278 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10279 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10280 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10281 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10282 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10283 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10284 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10285 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10286 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10287 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10288 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10289 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10290 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10291 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10292 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10293 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10294 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10295 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10296 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10297 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10298 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10299 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10300 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10301 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10302 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10303 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10304 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10305 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10306 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10307 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10308 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10309 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10310 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10311 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10312 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10313 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10314 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10315 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10316 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10317 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10318 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10319 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10320 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10321 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10322 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10323 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10324 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10325 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10326 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10327 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10328 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10329 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10330 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10331 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10332 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10333 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10334 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10335 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10336 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10337 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10338 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10339 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10340 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10341 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10342 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10343 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10344 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10345 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10346 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10347 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10348 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10349 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10350 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10351 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10352 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10353 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10354 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');
10355 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');
10356 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10357 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10358 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10359 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10360 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10361 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10362 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10363 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10364 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10365 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10366
10367 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10368 DECLARE
10369         ldr         RECORD;
10370         tval        TEXT;
10371         tval_rec    RECORD;
10372         bval        TEXT;
10373         bval_rec    RECORD;
10374     retval      config.marc21_rec_type_map%ROWTYPE;
10375 BEGIN
10376     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10377
10378     IF ldr.id IS NULL THEN
10379         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10380         RETURN retval;
10381     END IF;
10382
10383     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10384     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10385
10386
10387     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10388     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10389
10390     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10391
10392     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10393
10394
10395     IF retval.code IS NULL THEN
10396         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10397     END IF;
10398
10399     RETURN retval;
10400 END;
10401 $func$ LANGUAGE PLPGSQL;
10402
10403 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10404 DECLARE
10405     rtype       TEXT;
10406     ff_pos      RECORD;
10407     tag_data    RECORD;
10408     val         TEXT;
10409 BEGIN
10410     rtype := (biblio.marc21_record_type( rid )).code;
10411     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10412         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10413             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10414             RETURN val;
10415         END LOOP;
10416         val := REPEAT( ff_pos.default_val, ff_pos.length );
10417         RETURN val;
10418     END LOOP;
10419
10420     RETURN NULL;
10421 END;
10422 $func$ LANGUAGE PLPGSQL;
10423
10424 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10425 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10426 DECLARE
10427     rowid   INT := 0;
10428     _007    RECORD;
10429     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10430     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10431     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10432     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10433 BEGIN
10434
10435     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10436
10437     IF _007.id IS NOT NULL THEN
10438         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10439
10440         IF ptype.ptype_key IS NOT NULL THEN
10441             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10442                 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 );
10443
10444                 IF pval.id IS NOT NULL THEN
10445                     rowid := rowid + 1;
10446                     retval.id := rowid;
10447                     retval.record := rid;
10448                     retval.ptype := ptype.ptype_key;
10449                     retval.subfield := psf.id;
10450                     retval.value := pval.id;
10451                     RETURN NEXT retval;
10452                 END IF;
10453
10454             END LOOP;
10455         END IF;
10456     END IF;
10457
10458     RETURN;
10459 END;
10460 $func$ LANGUAGE PLPGSQL;
10461
10462 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10463 DROP VIEW IF EXISTS money.open_usr_summary;
10464 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10465
10466 -- The view should supply defaults for numeric (amount) columns
10467 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10468     SELECT  xact.id,
10469         xact.usr,
10470         xact.xact_start,
10471         xact.xact_finish,
10472         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10473         credit.payment_ts AS last_payment_ts,
10474         credit.note AS last_payment_note,
10475         credit.payment_type AS last_payment_type,
10476         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10477         debit.billing_ts AS last_billing_ts,
10478         debit.note AS last_billing_note,
10479         debit.billing_type AS last_billing_type,
10480         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10481         p.relname AS xact_type
10482       FROM  money.billable_xact xact
10483         JOIN pg_class p ON xact.tableoid = p.oid
10484         LEFT JOIN (
10485             SELECT  billing.xact,
10486                 sum(billing.amount) AS amount,
10487                 max(billing.billing_ts) AS billing_ts,
10488                 last(billing.note) AS note,
10489                 last(billing.billing_type) AS billing_type
10490               FROM  money.billing
10491               WHERE billing.voided IS FALSE
10492               GROUP BY billing.xact
10493             ) debit ON xact.id = debit.xact
10494         LEFT JOIN (
10495             SELECT  payment_view.xact,
10496                 sum(payment_view.amount) AS amount,
10497                 max(payment_view.payment_ts) AS payment_ts,
10498                 last(payment_view.note) AS note,
10499                 last(payment_view.payment_type) AS payment_type
10500               FROM  money.payment_view
10501               WHERE payment_view.voided IS FALSE
10502               GROUP BY payment_view.xact
10503             ) credit ON xact.id = credit.xact
10504       ORDER BY debit.billing_ts, credit.payment_ts;
10505
10506 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10507     SELECT * FROM money.billable_xact_summary_location_view
10508     WHERE xact_finish IS NULL;
10509
10510 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10511     SELECT 
10512         usr,
10513         SUM(total_paid) AS total_paid,
10514         SUM(total_owed) AS total_owed,
10515         SUM(balance_owed) AS balance_owed
10516     FROM  money.materialized_billable_xact_summary
10517     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10518     GROUP BY usr;
10519
10520 CREATE OR REPLACE VIEW money.usr_summary AS
10521     SELECT 
10522         usr, 
10523         sum(total_paid) AS total_paid, 
10524         sum(total_owed) AS total_owed, 
10525         sum(balance_owed) AS balance_owed
10526     FROM money.materialized_billable_xact_summary
10527     GROUP BY usr;
10528
10529 CREATE OR REPLACE VIEW money.open_usr_summary AS
10530     SELECT 
10531         usr, 
10532         sum(total_paid) AS total_paid, 
10533         sum(total_owed) AS total_owed, 
10534         sum(balance_owed) AS balance_owed
10535     FROM money.materialized_billable_xact_summary
10536     WHERE xact_finish IS NULL
10537     GROUP BY usr;
10538
10539 -- 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;
10540
10541 CREATE TABLE config.biblio_fingerprint (
10542         id                      SERIAL  PRIMARY KEY,
10543         name            TEXT    NOT NULL, 
10544         xpath           TEXT    NOT NULL,
10545     first_word  BOOL    NOT NULL DEFAULT FALSE,
10546         format          TEXT    NOT NULL DEFAULT 'marcxml'
10547 );
10548
10549 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10550     VALUES (
10551         'Title',
10552         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10553             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10554             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10555             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10556             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10557         'marcxml'
10558     );
10559
10560 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10561     VALUES (
10562         'Author',
10563         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10564             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10565             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10566             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10567             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10568         'marcxml',
10569         TRUE
10570     );
10571
10572 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10573 DECLARE
10574     qual        INT;
10575     ldr         TEXT;
10576     tval        TEXT;
10577     tval_rec    RECORD;
10578     bval        TEXT;
10579     bval_rec    RECORD;
10580     type_map    RECORD;
10581     ff_pos      RECORD;
10582     ff_tag_data TEXT;
10583 BEGIN
10584
10585     IF marc IS NULL OR marc = '' THEN
10586         RETURN NULL;
10587     END IF;
10588
10589     -- First, the count of tags
10590     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10591
10592     -- now go through a bunch of pain to get the record type
10593     IF best_type IS NOT NULL THEN
10594         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10595
10596         IF ldr IS NOT NULL THEN
10597             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10598             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10599
10600
10601             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10602             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10603
10604             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10605
10606             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10607
10608             IF type_map.code IS NOT NULL THEN
10609                 IF best_type = type_map.code THEN
10610                     qual := qual + qual / 2;
10611                 END IF;
10612
10613                 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
10614                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10615                     IF ff_tag_data = best_lang THEN
10616                             qual := qual + 100;
10617                     END IF;
10618                 END LOOP;
10619             END IF;
10620         END IF;
10621     END IF;
10622
10623     -- Now look for some quality metrics
10624     -- DCL record?
10625     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10626         qual := qual + 10;
10627     END IF;
10628
10629     -- From OCLC?
10630     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10631         qual := qual + 10;
10632     END IF;
10633
10634     RETURN qual;
10635
10636 END;
10637 $func$ LANGUAGE PLPGSQL;
10638
10639 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10640 DECLARE
10641     idx     config.biblio_fingerprint%ROWTYPE;
10642     xfrm        config.xml_transform%ROWTYPE;
10643     prev_xfrm   TEXT;
10644     transformed_xml TEXT;
10645     xml_node    TEXT;
10646     xml_node_list   TEXT[];
10647     raw_text    TEXT;
10648     output_text TEXT := '';
10649 BEGIN
10650
10651     IF marc IS NULL OR marc = '' THEN
10652         RETURN NULL;
10653     END IF;
10654
10655     -- Loop over the indexing entries
10656     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10657
10658         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10659
10660         -- See if we can skip the XSLT ... it's expensive
10661         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10662             -- Can't skip the transform
10663             IF xfrm.xslt <> '---' THEN
10664                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10665             ELSE
10666                 transformed_xml := marc;
10667             END IF;
10668
10669             prev_xfrm := xfrm.name;
10670         END IF;
10671
10672         raw_text := COALESCE(
10673             naco_normalize(
10674                 ARRAY_TO_STRING(
10675                     oils_xpath(
10676                         '//text()',
10677                         (oils_xpath(
10678                             idx.xpath,
10679                             transformed_xml,
10680                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10681                         ))[1]
10682                     ),
10683                     ''
10684                 )
10685             ),
10686             ''
10687         );
10688
10689         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10690         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10691
10692         IF idx.first_word IS TRUE THEN
10693             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10694         END IF;
10695
10696         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10697
10698     END LOOP;
10699
10700     RETURN output_text;
10701
10702 END;
10703 $func$ LANGUAGE PLPGSQL;
10704
10705 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10706 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10707 BEGIN
10708
10709     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10710
10711     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10712         RETURN NEW;
10713     END IF;
10714
10715     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10716     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10717
10718     RETURN NEW;
10719
10720 END;
10721 $func$ LANGUAGE PLPGSQL;
10722
10723 CREATE TABLE config.internal_flag (
10724     name    TEXT    PRIMARY KEY,
10725     value   TEXT,
10726     enabled BOOL    NOT NULL DEFAULT FALSE
10727 );
10728 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10729 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10730 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10731 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10732 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10733 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10734 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10735 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10736 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10737 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10738
10739 CREATE TABLE authority.bib_linking (
10740     id          BIGSERIAL   PRIMARY KEY,
10741     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10742     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10743 );
10744 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10745 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10746
10747 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10748     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10749 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10750
10751 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10752     DELETE FROM authority.bib_linking WHERE bib = $1;
10753     INSERT INTO authority.bib_linking (bib, authority)
10754         SELECT  y.bib,
10755                 y.authority
10756           FROM (    SELECT  DISTINCT $1 AS bib,
10757                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10758                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10759                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10760                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10761     SELECT $1;
10762 $func$ LANGUAGE SQL;
10763
10764 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10765 BEGIN
10766     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10767     IF NOT FOUND THEN
10768         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10769     END IF;
10770     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)
10771         SELECT  bib_id,
10772                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10773                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10774                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10775                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10776                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10777                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10778                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10779                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10780                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10781                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10782                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10783                 (   SELECT  v.value
10784                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10785                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10786                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10787                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10788                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
10789                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
10790
10791     RETURN;
10792 END;
10793 $func$ LANGUAGE PLPGSQL;
10794
10795 CREATE TABLE config.metabib_class (
10796     name    TEXT    PRIMARY KEY,
10797     label   TEXT    NOT NULL UNIQUE
10798 );
10799
10800 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10801 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10802 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10803 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10804 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10805
10806 CREATE TABLE metabib.facet_entry (
10807         id              BIGSERIAL       PRIMARY KEY,
10808         source          BIGINT          NOT NULL,
10809         field           INT             NOT NULL,
10810         value           TEXT            NOT NULL
10811 );
10812
10813 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10814 DECLARE
10815     fclass          RECORD;
10816     ind_data        metabib.field_entry_template%ROWTYPE;
10817 BEGIN
10818     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10819     IF NOT FOUND THEN
10820         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10821             -- RAISE NOTICE 'Emptying out %', fclass.name;
10822             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10823         END LOOP;
10824         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10825     END IF;
10826
10827     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10828         IF ind_data.field < 0 THEN
10829             ind_data.field = -1 * ind_data.field;
10830             INSERT INTO metabib.facet_entry (field, source, value)
10831                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10832         ELSE
10833             EXECUTE $$
10834                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10835                     VALUES ($$ ||
10836                         quote_literal(ind_data.field) || $$, $$ ||
10837                         quote_literal(ind_data.source) || $$, $$ ||
10838                         quote_literal(ind_data.value) ||
10839                     $$);$$;
10840         END IF;
10841
10842     END LOOP;
10843
10844     RETURN;
10845 END;
10846 $func$ LANGUAGE PLPGSQL;
10847
10848 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
10849 DECLARE
10850     uris            TEXT[];
10851     uri_xml         TEXT;
10852     uri_label       TEXT;
10853     uri_href        TEXT;
10854     uri_use         TEXT;
10855     uri_owner       TEXT;
10856     uri_owner_id    INT;
10857     uri_id          INT;
10858     uri_cn_id       INT;
10859     uri_map_id      INT;
10860 BEGIN
10861
10862     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
10863     IF ARRAY_UPPER(uris,1) > 0 THEN
10864         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
10865             -- First we pull info out of the 856
10866             uri_xml     := uris[i];
10867
10868             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
10869             CONTINUE WHEN uri_href IS NULL;
10870
10871             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10872             CONTINUE WHEN uri_label IS NULL;
10873
10874             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
10875             CONTINUE WHEN uri_owner IS NULL;
10876
10877             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10878
10879             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
10880
10881             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
10882             CONTINUE WHEN NOT FOUND;
10883
10884             -- now we look for a matching uri
10885             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10886             IF NOT FOUND THEN -- create one
10887                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
10888                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10889             END IF;
10890
10891             -- we need a call number to link through
10892             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;
10893             IF NOT FOUND THEN
10894                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
10895                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
10896                 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;
10897             END IF;
10898
10899             -- now, link them if they're not already
10900             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
10901             IF NOT FOUND THEN
10902                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
10903             END IF;
10904
10905         END LOOP;
10906     END IF;
10907
10908     RETURN;
10909 END;
10910 $func$ LANGUAGE PLPGSQL;
10911
10912 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
10913 DECLARE
10914     source_count    INT;
10915     old_mr          BIGINT;
10916     tmp_mr          metabib.metarecord%ROWTYPE;
10917     deleted_mrs     BIGINT[];
10918 BEGIN
10919
10920     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
10921
10922     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
10923
10924         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
10925             old_mr := tmp_mr.id;
10926         ELSE
10927             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
10928             IF source_count = 0 THEN -- No other records
10929                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
10930                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
10931             END IF;
10932         END IF;
10933
10934     END LOOP;
10935
10936     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
10937         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
10938         IF old_mr IS NULL THEN -- nope, create one and grab its id
10939             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
10940             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
10941         ELSE -- indeed there is. update it with a null cache and recalcualated master record
10942             UPDATE  metabib.metarecord
10943               SET   mods = NULL,
10944                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
10945               WHERE id = old_mr;
10946         END IF;
10947     ELSE -- there was one we already attached to, update its mods cache and master_record
10948         UPDATE  metabib.metarecord
10949           SET   mods = NULL,
10950                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
10951           WHERE id = old_mr;
10952     END IF;
10953
10954     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
10955
10956     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
10957         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
10958     END IF;
10959
10960     RETURN old_mr;
10961
10962 END;
10963 $func$ LANGUAGE PLPGSQL;
10964
10965 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
10966 BEGIN
10967     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10968     IF NOT FOUND THEN
10969         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
10970     END IF;
10971     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
10972         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
10973
10974     RETURN;
10975 END;
10976 $func$ LANGUAGE PLPGSQL;
10977
10978 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
10979 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
10980 BEGIN
10981
10982     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
10983         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
10984         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
10985         RETURN NEW; -- and we're done
10986     END IF;
10987
10988     IF TG_OP = 'UPDATE' THEN -- re-ingest?
10989         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
10990
10991         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
10992             RETURN NEW;
10993         END IF;
10994     END IF;
10995
10996     -- Record authority linking
10997     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
10998     IF NOT FOUND THEN
10999         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11000     END IF;
11001
11002     -- Flatten and insert the mfr data
11003     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11004     IF NOT FOUND THEN
11005         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11006         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11007         IF NOT FOUND THEN
11008             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
11009         END IF;
11010     END IF;
11011
11012     -- Gather and insert the field entry data
11013     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11014
11015     -- Located URI magic
11016     IF TG_OP = 'INSERT' THEN
11017         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11018         IF NOT FOUND THEN
11019             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11020         END IF;
11021     ELSE
11022         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11023         IF NOT FOUND THEN
11024             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11025         END IF;
11026     END IF;
11027
11028     -- (re)map metarecord-bib linking
11029     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11030         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11031         IF NOT FOUND THEN
11032             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11033         END IF;
11034     ELSE -- we're doing an update, and we're not deleted, remap
11035         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11036         IF NOT FOUND THEN
11037             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11038         END IF;
11039     END IF;
11040
11041     RETURN NEW;
11042 END;
11043 $func$ LANGUAGE PLPGSQL;
11044
11045 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
11046 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 ();
11047
11048 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
11049
11050 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
11051 DECLARE
11052     xpath_list  TEXT[];
11053     select_list TEXT[];
11054     where_list  TEXT[];
11055     q           TEXT;
11056     out_record  RECORD;
11057     empty_test  RECORD;
11058 BEGIN
11059     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
11060  
11061     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
11062  
11063     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
11064         IF xpath_list[i] = 'null()' THEN
11065             select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
11066         ELSE
11067             select_list := ARRAY_APPEND(
11068                 select_list,
11069                 $sel$
11070                 EXPLODE_ARRAY(
11071                     COALESCE(
11072                         NULLIF(
11073                             oils_xpath(
11074                                 $sel$ ||
11075                                     quote_literal(
11076                                         CASE
11077                                             WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
11078                                             ELSE xpath_list[i] || '//text()'
11079                                         END
11080                                     ) ||
11081                                 $sel$,
11082                                 $sel$ || document_field || $sel$
11083                             ),
11084                            '{}'::TEXT[]
11085                         ),
11086                         '{NULL}'::TEXT[]
11087                     )
11088                 ) AS c_$sel$ || i
11089             );
11090             where_list := ARRAY_APPEND(
11091                 where_list,
11092                 'c_' || i || ' IS NOT NULL'
11093             );
11094         END IF;
11095     END LOOP;
11096  
11097     q := $q$
11098 SELECT * FROM (
11099     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11100 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
11101     -- RAISE NOTICE 'query: %', q;
11102  
11103     FOR out_record IN EXECUTE q LOOP
11104         RETURN NEXT out_record;
11105     END LOOP;
11106  
11107     RETURN;
11108 END;
11109 $func$ LANGUAGE PLPGSQL IMMUTABLE;
11110
11111 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11112 DECLARE
11113
11114     owning_lib      TEXT;
11115     circ_lib        TEXT;
11116     call_number     TEXT;
11117     copy_number     TEXT;
11118     status          TEXT;
11119     location        TEXT;
11120     circulate       TEXT;
11121     deposit         TEXT;
11122     deposit_amount  TEXT;
11123     ref             TEXT;
11124     holdable        TEXT;
11125     price           TEXT;
11126     barcode         TEXT;
11127     circ_modifier   TEXT;
11128     circ_as_type    TEXT;
11129     alert_message   TEXT;
11130     opac_visible    TEXT;
11131     pub_note        TEXT;
11132     priv_note       TEXT;
11133
11134     attr_def        RECORD;
11135     tmp_attr_set    RECORD;
11136     attr_set        vandelay.import_item%ROWTYPE;
11137
11138     xpath           TEXT;
11139
11140 BEGIN
11141
11142     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11143
11144     IF FOUND THEN
11145
11146         attr_set.definition := attr_def.id; 
11147     
11148         -- Build the combined XPath
11149     
11150         owning_lib :=
11151             CASE
11152                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11153                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11154                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11155             END;
11156     
11157         circ_lib :=
11158             CASE
11159                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11160                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11161                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11162             END;
11163     
11164         call_number :=
11165             CASE
11166                 WHEN attr_def.call_number IS NULL THEN 'null()'
11167                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11168                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11169             END;
11170     
11171         copy_number :=
11172             CASE
11173                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11174                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11175                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11176             END;
11177     
11178         status :=
11179             CASE
11180                 WHEN attr_def.status IS NULL THEN 'null()'
11181                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11182                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11183             END;
11184     
11185         location :=
11186             CASE
11187                 WHEN attr_def.location IS NULL THEN 'null()'
11188                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11189                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11190             END;
11191     
11192         circulate :=
11193             CASE
11194                 WHEN attr_def.circulate IS NULL THEN 'null()'
11195                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11196                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11197             END;
11198     
11199         deposit :=
11200             CASE
11201                 WHEN attr_def.deposit IS NULL THEN 'null()'
11202                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11203                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11204             END;
11205     
11206         deposit_amount :=
11207             CASE
11208                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11209                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11210                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11211             END;
11212     
11213         ref :=
11214             CASE
11215                 WHEN attr_def.ref IS NULL THEN 'null()'
11216                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11217                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11218             END;
11219     
11220         holdable :=
11221             CASE
11222                 WHEN attr_def.holdable IS NULL THEN 'null()'
11223                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11224                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11225             END;
11226     
11227         price :=
11228             CASE
11229                 WHEN attr_def.price IS NULL THEN 'null()'
11230                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11231                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11232             END;
11233     
11234         barcode :=
11235             CASE
11236                 WHEN attr_def.barcode IS NULL THEN 'null()'
11237                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11238                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11239             END;
11240     
11241         circ_modifier :=
11242             CASE
11243                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11244                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11245                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11246             END;
11247     
11248         circ_as_type :=
11249             CASE
11250                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11251                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11252                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11253             END;
11254     
11255         alert_message :=
11256             CASE
11257                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11258                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11259                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11260             END;
11261     
11262         opac_visible :=
11263             CASE
11264                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11265                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11266                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11267             END;
11268
11269         pub_note :=
11270             CASE
11271                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11272                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11273                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11274             END;
11275         priv_note :=
11276             CASE
11277                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11278                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11279                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11280             END;
11281     
11282     
11283         xpath := 
11284             owning_lib      || '|' || 
11285             circ_lib        || '|' || 
11286             call_number     || '|' || 
11287             copy_number     || '|' || 
11288             status          || '|' || 
11289             location        || '|' || 
11290             circulate       || '|' || 
11291             deposit         || '|' || 
11292             deposit_amount  || '|' || 
11293             ref             || '|' || 
11294             holdable        || '|' || 
11295             price           || '|' || 
11296             barcode         || '|' || 
11297             circ_modifier   || '|' || 
11298             circ_as_type    || '|' || 
11299             alert_message   || '|' || 
11300             pub_note        || '|' || 
11301             priv_note       || '|' || 
11302             opac_visible;
11303
11304         -- RAISE NOTICE 'XPath: %', xpath;
11305         
11306         FOR tmp_attr_set IN
11307                 SELECT  *
11308                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11309                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11310                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11311                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11312         LOOP
11313     
11314             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11315             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11316
11317             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11318             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11319     
11320             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11321             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11322             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11323     
11324             SELECT  id INTO attr_set.location
11325               FROM  asset.copy_location
11326               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11327                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11328     
11329             attr_set.circulate      :=
11330                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11331                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11332
11333             attr_set.deposit        :=
11334                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11335                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11336
11337             attr_set.holdable       :=
11338                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11339                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11340
11341             attr_set.opac_visible   :=
11342                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11343                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11344
11345             attr_set.ref            :=
11346                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11347                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11348     
11349             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11350             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11351             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11352     
11353             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11354             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11355             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11356             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11357             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11358             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11359             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11360             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11361     
11362             RETURN NEXT attr_set;
11363     
11364         END LOOP;
11365     
11366     END IF;
11367
11368     RETURN;
11369
11370 END;
11371 $$ LANGUAGE PLPGSQL;
11372
11373 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11374 DECLARE
11375     attr_def    BIGINT;
11376     item_data   vandelay.import_item%ROWTYPE;
11377 BEGIN
11378
11379     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11380
11381     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11382         INSERT INTO vandelay.import_item (
11383             record,
11384             definition,
11385             owning_lib,
11386             circ_lib,
11387             call_number,
11388             copy_number,
11389             status,
11390             location,
11391             circulate,
11392             deposit,
11393             deposit_amount,
11394             ref,
11395             holdable,
11396             price,
11397             barcode,
11398             circ_modifier,
11399             circ_as_type,
11400             alert_message,
11401             pub_note,
11402             priv_note,
11403             opac_visible
11404         ) VALUES (
11405             NEW.id,
11406             item_data.definition,
11407             item_data.owning_lib,
11408             item_data.circ_lib,
11409             item_data.call_number,
11410             item_data.copy_number,
11411             item_data.status,
11412             item_data.location,
11413             item_data.circulate,
11414             item_data.deposit,
11415             item_data.deposit_amount,
11416             item_data.ref,
11417             item_data.holdable,
11418             item_data.price,
11419             item_data.barcode,
11420             item_data.circ_modifier,
11421             item_data.circ_as_type,
11422             item_data.alert_message,
11423             item_data.pub_note,
11424             item_data.priv_note,
11425             item_data.opac_visible
11426         );
11427     END LOOP;
11428
11429     RETURN NULL;
11430 END;
11431 $func$ LANGUAGE PLPGSQL;
11432
11433 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11434 BEGIN
11435     EXECUTE $$
11436         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11437     $$;
11438         RETURN TRUE;
11439 END;
11440 $creator$ LANGUAGE 'plpgsql';
11441
11442 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11443 BEGIN
11444     EXECUTE $$
11445         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11446             audit_id    BIGINT                          PRIMARY KEY,
11447             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11448             audit_action        TEXT                            NOT NULL,
11449             LIKE $$ || sch || $$.$$ || tbl || $$
11450         );
11451     $$;
11452         RETURN TRUE;
11453 END;
11454 $creator$ LANGUAGE 'plpgsql';
11455
11456 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11457 BEGIN
11458     EXECUTE $$
11459         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11460         RETURNS TRIGGER AS $func$
11461         BEGIN
11462             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11463                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11464                     now(),
11465                     SUBSTR(TG_OP,1,1),
11466                     OLD.*;
11467             RETURN NULL;
11468         END;
11469         $func$ LANGUAGE 'plpgsql';
11470     $$;
11471         RETURN TRUE;
11472 END;
11473 $creator$ LANGUAGE 'plpgsql';
11474
11475 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11476 BEGIN
11477     EXECUTE $$
11478         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11479             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11480             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11481     $$;
11482         RETURN TRUE;
11483 END;
11484 $creator$ LANGUAGE 'plpgsql';
11485
11486 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11487 BEGIN
11488     EXECUTE $$
11489         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11490             SELECT      -1, now() as audit_time, '-' as audit_action, *
11491               FROM      $$ || sch || $$.$$ || tbl || $$
11492                 UNION ALL
11493             SELECT      *
11494               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11495     $$;
11496         RETURN TRUE;
11497 END;
11498 $creator$ LANGUAGE 'plpgsql';
11499
11500 -- The main event
11501
11502 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11503 BEGIN
11504     PERFORM acq.create_acq_seq(sch, tbl);
11505     PERFORM acq.create_acq_history(sch, tbl);
11506     PERFORM acq.create_acq_func(sch, tbl);
11507     PERFORM acq.create_acq_update_trigger(sch, tbl);
11508     PERFORM acq.create_acq_lifecycle(sch, tbl);
11509     RETURN TRUE;
11510 END;
11511 $creator$ LANGUAGE 'plpgsql';
11512
11513 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11514
11515 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11516     SELECT  fund.id AS fund,
11517             fund_debit.encumbrance AS encumbrance,
11518             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11519       FROM acq.fund AS fund
11520                         LEFT JOIN acq.fund_debit AS fund_debit
11521                                 ON ( fund.id = fund_debit.fund )
11522       GROUP BY 1,2;
11523
11524 CREATE TABLE acq.debit_attribution (
11525         id                     INT         NOT NULL PRIMARY KEY,
11526         fund_debit             INT         NOT NULL
11527                                            REFERENCES acq.fund_debit
11528                                            DEFERRABLE INITIALLY DEFERRED,
11529     debit_amount           NUMERIC     NOT NULL,
11530         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11531                                            DEFERRABLE INITIALLY DEFERRED,
11532     credit_amount          NUMERIC
11533 );
11534
11535 CREATE INDEX acq_attribution_debit_idx
11536         ON acq.debit_attribution( fund_debit );
11537
11538 CREATE INDEX acq_attribution_credit_idx
11539         ON acq.debit_attribution( funding_source_credit );
11540
11541 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11542 /*
11543 Function to attribute expenditures and encumbrances to funding source credits,
11544 and thereby to funding sources.
11545
11546 Read the debits in chonological order, attributing each one to one or
11547 more funding source credits.  Constraints:
11548
11549 1. Don't attribute more to a credit than the amount of the credit.
11550
11551 2. For a given fund, don't attribute more to a funding source than the
11552 source has allocated to that fund.
11553
11554 3. Attribute debits to credits with deadlines before attributing them to
11555 credits without deadlines.  Otherwise attribute to the earliest credits
11556 first, based on the deadline date when present, or on the effective date
11557 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11558 This ordering is defined by an ORDER BY clause on the view
11559 acq.ordered_funding_source_credit.
11560
11561 Start by truncating the table acq.debit_attribution.  Then insert a row
11562 into that table for each attribution.  If a debit cannot be fully
11563 attributed, insert a row for the unattributable balance, with the 
11564 funding_source_credit and credit_amount columns NULL.
11565 */
11566 DECLARE
11567         curr_fund_source_bal RECORD;
11568         seqno                INT;     -- sequence num for credits applicable to a fund
11569         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11570         fc                   RECORD;  -- used for loading t_fund_credit table
11571         sc                   RECORD;  -- used for loading t_fund_credit table
11572         --
11573         -- Used exclusively in the main loop:
11574         --
11575         deb                 RECORD;   -- current row from acq.fund_debit table
11576         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11577         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11578         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11579         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11580         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11581         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11582         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11583         attrib_count        INT;      -- populates id of acq.debit_attribution
11584 BEGIN
11585         --
11586         -- Load a temporary table.  For each combination of fund and funding source,
11587         -- load an entry with the total amount allocated to that fund by that source.
11588         -- This sum may reflect transfers as well as original allocations.  We will
11589         -- reduce this balance whenever we attribute debits to it.
11590         --
11591         CREATE TEMP TABLE t_fund_source_bal
11592         ON COMMIT DROP AS
11593                 SELECT
11594                         fund AS fund,
11595                         funding_source AS source,
11596                         sum( amount ) AS balance
11597                 FROM
11598                         acq.fund_allocation
11599                 GROUP BY
11600                         fund,
11601                         funding_source
11602                 HAVING
11603                         sum( amount ) > 0;
11604         --
11605         CREATE INDEX t_fund_source_bal_idx
11606                 ON t_fund_source_bal( fund, source );
11607         -------------------------------------------------------------------------------
11608         --
11609         -- Load another temporary table.  For each fund, load zero or more
11610         -- funding source credits from which that fund can get money.
11611         --
11612         CREATE TEMP TABLE t_fund_credit (
11613                 fund        INT,
11614                 seq         INT,
11615                 credit      INT
11616         ) ON COMMIT DROP;
11617         --
11618         FOR fc IN
11619                 SELECT DISTINCT fund
11620                 FROM acq.fund_allocation
11621                 ORDER BY fund
11622         LOOP                  -- Loop over the funds
11623                 seqno := 1;
11624                 FOR sc IN
11625                         SELECT
11626                                 ofsc.id
11627                         FROM
11628                                 acq.ordered_funding_source_credit AS ofsc
11629                         WHERE
11630                                 ofsc.funding_source IN
11631                                 (
11632                                         SELECT funding_source
11633                                         FROM acq.fund_allocation
11634                                         WHERE fund = fc.fund
11635                                 )
11636                 ORDER BY
11637                     ofsc.sort_priority,
11638                     ofsc.sort_date,
11639                     ofsc.id
11640                 LOOP                        -- Add each credit to the list
11641                         INSERT INTO t_fund_credit (
11642                                 fund,
11643                                 seq,
11644                                 credit
11645                         ) VALUES (
11646                                 fc.fund,
11647                                 seqno,
11648                                 sc.id
11649                         );
11650                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11651                         seqno := seqno + 1;
11652                 END LOOP;     -- Loop over credits for a given fund
11653         END LOOP;         -- Loop over funds
11654         --
11655         CREATE INDEX t_fund_credit_idx
11656                 ON t_fund_credit( fund, seq );
11657         -------------------------------------------------------------------------------
11658         --
11659         -- Load yet another temporary table.  This one is a list of funding source
11660         -- credits, with their balances.  We shall reduce those balances as we
11661         -- attribute debits to them.
11662         --
11663         CREATE TEMP TABLE t_credit
11664         ON COMMIT DROP AS
11665         SELECT
11666             fsc.id AS credit,
11667             fsc.funding_source AS source,
11668             fsc.amount AS balance,
11669             fs.currency_type AS currency_type
11670         FROM
11671             acq.funding_source_credit AS fsc,
11672             acq.funding_source fs
11673         WHERE
11674             fsc.funding_source = fs.id
11675                         AND fsc.amount > 0;
11676         --
11677         CREATE INDEX t_credit_idx
11678                 ON t_credit( credit );
11679         --
11680         -------------------------------------------------------------------------------
11681         --
11682         -- Now that we have loaded the lookup tables: loop through the debits,
11683         -- attributing each one to one or more funding source credits.
11684         -- 
11685         truncate table acq.debit_attribution;
11686         --
11687         attrib_count := 0;
11688         FOR deb in
11689                 SELECT
11690                         fd.id,
11691                         fd.fund,
11692                         fd.amount,
11693                         f.currency_type,
11694                         fd.encumbrance
11695                 FROM
11696                         acq.fund_debit fd,
11697                         acq.fund f
11698                 WHERE
11699                         fd.fund = f.id
11700                 ORDER BY
11701                         fd.id
11702         LOOP
11703                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11704                 --
11705                 debit_balance := deb.amount;
11706                 --
11707                 -- Loop over the funding source credits that are eligible
11708                 -- to pay for this debit
11709                 --
11710                 FOR fund_credit IN
11711                         SELECT
11712                                 credit
11713                         FROM
11714                                 t_fund_credit
11715                         WHERE
11716                                 fund = deb.fund
11717                         ORDER BY
11718                                 seq
11719                 LOOP
11720                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11721                         --
11722                         -- Look up the balance for this credit.  If it's zero, then
11723                         -- it's not useful, so treat it as if you didn't find it.
11724                         -- (Actually there shouldn't be any zero balances in the table,
11725                         -- but we check just to make sure.)
11726                         --
11727                         SELECT *
11728                         INTO curr_credit_bal
11729                         FROM t_credit
11730                         WHERE
11731                                 credit = fund_credit.credit
11732                                 AND balance > 0;
11733                         --
11734                         IF curr_credit_bal IS NULL THEN
11735                                 --
11736                                 -- This credit is exhausted; try the next one.
11737                                 --
11738                                 CONTINUE;
11739                         END IF;
11740                         --
11741                         --
11742                         -- At this point we have an applicable credit with some money left.
11743                         -- Now see if the relevant funding_source has any money left.
11744                         --
11745                         -- Look up the balance of the allocation for this combination of
11746                         -- fund and source.  If you find such an entry, but it has a zero
11747                         -- balance, then it's not useful, so treat it as unfound.
11748                         -- (Actually there shouldn't be any zero balances in the table,
11749                         -- but we check just to make sure.)
11750                         --
11751                         SELECT *
11752                         INTO curr_fund_source_bal
11753                         FROM t_fund_source_bal
11754                         WHERE
11755                                 fund = deb.fund
11756                                 AND source = curr_credit_bal.source
11757                                 AND balance > 0;
11758                         --
11759                         IF curr_fund_source_bal IS NULL THEN
11760                                 --
11761                                 -- This fund/source doesn't exist or is already exhausted,
11762                                 -- so we can't use this credit.  Go on to the next one.
11763                                 --
11764                                 CONTINUE;
11765                         END IF;
11766                         --
11767                         -- Convert the available balances to the currency of the fund
11768                         --
11769                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11770                                 curr_credit_bal.currency_type, deb.currency_type );
11771                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11772                                 curr_credit_bal.currency_type, deb.currency_type );
11773                         --
11774                         -- Determine how much we can attribute to this credit: the minimum
11775                         -- of the debit amount, the fund/source balance, and the
11776                         -- credit balance
11777                         --
11778                         --RAISE NOTICE '   deb bal %', debit_balance;
11779                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11780                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11781                         --
11782                         conv_attr_amount := NULL;
11783                         attr_amount := debit_balance;
11784                         --
11785                         IF attr_amount > conv_alloc_balance THEN
11786                                 attr_amount := conv_alloc_balance;
11787                                 conv_attr_amount := curr_fund_source_bal.balance;
11788                         END IF;
11789                         IF attr_amount > conv_cred_balance THEN
11790                                 attr_amount := conv_cred_balance;
11791                                 conv_attr_amount := curr_credit_bal.balance;
11792                         END IF;
11793                         --
11794                         -- If we're attributing all of one of the balances, then that's how
11795                         -- much we will deduct from the balances, and we already captured
11796                         -- that amount above.  Otherwise we must convert the amount of the
11797                         -- attribution from the currency of the fund back to the currency of
11798                         -- the funding source.
11799                         --
11800                         IF conv_attr_amount IS NULL THEN
11801                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11802                                         deb.currency_type, curr_credit_bal.currency_type );
11803                         END IF;
11804                         --
11805                         -- Insert a row to record the attribution
11806                         --
11807                         attrib_count := attrib_count + 1;
11808                         INSERT INTO acq.debit_attribution (
11809                                 id,
11810                                 fund_debit,
11811                                 debit_amount,
11812                                 funding_source_credit,
11813                                 credit_amount
11814                         ) VALUES (
11815                                 attrib_count,
11816                                 deb.id,
11817                                 attr_amount,
11818                                 curr_credit_bal.credit,
11819                                 conv_attr_amount
11820                         );
11821                         --
11822                         -- Subtract the attributed amount from the various balances
11823                         --
11824                         debit_balance := debit_balance - attr_amount;
11825                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11826                         --
11827                         IF curr_fund_source_bal.balance <= 0 THEN
11828                                 --
11829                                 -- This allocation is exhausted.  Delete it so
11830                                 -- that we don't waste time looking at it again.
11831                                 --
11832                                 DELETE FROM t_fund_source_bal
11833                                 WHERE
11834                                         fund = curr_fund_source_bal.fund
11835                                         AND source = curr_fund_source_bal.source;
11836                         ELSE
11837                                 UPDATE t_fund_source_bal
11838                                 SET balance = balance - conv_attr_amount
11839                                 WHERE
11840                                         fund = curr_fund_source_bal.fund
11841                                         AND source = curr_fund_source_bal.source;
11842                         END IF;
11843                         --
11844                         IF curr_credit_bal.balance <= 0 THEN
11845                                 --
11846                                 -- This funding source credit is exhausted.  Delete it
11847                                 -- so that we don't waste time looking at it again.
11848                                 --
11849                                 --DELETE FROM t_credit
11850                                 --WHERE
11851                                 --      credit = curr_credit_bal.credit;
11852                                 --
11853                                 DELETE FROM t_fund_credit
11854                                 WHERE
11855                                         credit = curr_credit_bal.credit;
11856                         ELSE
11857                                 UPDATE t_credit
11858                                 SET balance = curr_credit_bal.balance
11859                                 WHERE
11860                                         credit = curr_credit_bal.credit;
11861                         END IF;
11862                         --
11863                         -- Are we done with this debit yet?
11864                         --
11865                         IF debit_balance <= 0 THEN
11866                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
11867                         END IF;
11868                 END LOOP;       -- End loop over credits
11869                 --
11870                 IF debit_balance <> 0 THEN
11871                         --
11872                         -- We weren't able to attribute this debit, or at least not
11873                         -- all of it.  Insert a row for the unattributed balance.
11874                         --
11875                         attrib_count := attrib_count + 1;
11876                         INSERT INTO acq.debit_attribution (
11877                                 id,
11878                                 fund_debit,
11879                                 debit_amount,
11880                                 funding_source_credit,
11881                                 credit_amount
11882                         ) VALUES (
11883                                 attrib_count,
11884                                 deb.id,
11885                                 debit_balance,
11886                                 NULL,
11887                                 NULL
11888                         );
11889                 END IF;
11890         END LOOP;   -- End of loop over debits
11891 END;
11892 $$ LANGUAGE 'plpgsql';
11893
11894 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
11895 DECLARE
11896     query TEXT;
11897     output TEXT;
11898 BEGIN
11899     query := $q$
11900         SELECT  regexp_replace(
11901                     oils_xpath_string(
11902                         $q$ || quote_literal($3) || $q$,
11903                         marc,
11904                         ' '
11905                     ),
11906                     $q$ || quote_literal($4) || $q$,
11907                     '',
11908                     'g')
11909           FROM  $q$ || $1 || $q$
11910           WHERE id = $q$ || $2;
11911
11912     EXECUTE query INTO output;
11913
11914     -- RAISE NOTICE 'query: %, output; %', query, output;
11915
11916     RETURN output;
11917 END;
11918 $$ LANGUAGE PLPGSQL IMMUTABLE;
11919
11920 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
11921     SELECT extract_marc_field($1,$2,$3,'');
11922 $$ LANGUAGE SQL IMMUTABLE;
11923
11924 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
11925 DECLARE
11926     moved_objects INT := 0;
11927     source_cn     asset.call_number%ROWTYPE;
11928     target_cn     asset.call_number%ROWTYPE;
11929     metarec       metabib.metarecord%ROWTYPE;
11930     hold          action.hold_request%ROWTYPE;
11931     ser_rec       serial.record_entry%ROWTYPE;
11932     uri_count     INT := 0;
11933     counter       INT := 0;
11934     uri_datafield TEXT;
11935     uri_text      TEXT := '';
11936 BEGIN
11937
11938     -- move any 856 entries on records that have at least one MARC-mapped URI entry
11939     SELECT  INTO uri_count COUNT(*)
11940       FROM  asset.uri_call_number_map m
11941             JOIN asset.call_number cn ON (m.call_number = cn.id)
11942       WHERE cn.record = source_record;
11943
11944     IF uri_count > 0 THEN
11945
11946         SELECT  COUNT(*) INTO counter
11947           FROM  oils_xpath_table(
11948                     'id',
11949                     'marc',
11950                     'biblio.record_entry',
11951                     '//*[@tag="856"]',
11952                     'id=' || source_record
11953                 ) as t(i int,c text);
11954
11955         FOR i IN 1 .. counter LOOP
11956             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
11957                         ' tag="856"' || 
11958                         ' ind1="' || FIRST(ind1) || '"'  || 
11959                         ' ind2="' || FIRST(ind2) || '">' || 
11960                         array_to_string(
11961                             array_accum(
11962                                 '<subfield code="' || subfield || '">' ||
11963                                 regexp_replace(
11964                                     regexp_replace(
11965                                         regexp_replace(data,'&','&amp;','g'),
11966                                         '>', '&gt;', 'g'
11967                                     ),
11968                                     '<', '&lt;', 'g'
11969                                 ) || '</subfield>'
11970                             ), ''
11971                         ) || '</datafield>' INTO uri_datafield
11972               FROM  oils_xpath_table(
11973                         'id',
11974                         'marc',
11975                         'biblio.record_entry',
11976                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
11977                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
11978                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
11979                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
11980                         'id=' || source_record
11981                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
11982
11983             uri_text := uri_text || uri_datafield;
11984         END LOOP;
11985
11986         IF uri_text <> '' THEN
11987             UPDATE  biblio.record_entry
11988               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
11989               WHERE id = target_record;
11990         END IF;
11991
11992     END IF;
11993
11994     -- Find and move metarecords to the target record
11995     SELECT  INTO metarec *
11996       FROM  metabib.metarecord
11997       WHERE master_record = source_record;
11998
11999     IF FOUND THEN
12000         UPDATE  metabib.metarecord
12001           SET   master_record = target_record,
12002             mods = NULL
12003           WHERE id = metarec.id;
12004
12005         moved_objects := moved_objects + 1;
12006     END IF;
12007
12008     -- Find call numbers attached to the source ...
12009     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
12010
12011         SELECT  INTO target_cn *
12012           FROM  asset.call_number
12013           WHERE label = source_cn.label
12014             AND owning_lib = source_cn.owning_lib
12015             AND record = target_record;
12016
12017         -- ... and if there's a conflicting one on the target ...
12018         IF FOUND THEN
12019
12020             -- ... move the copies to that, and ...
12021             UPDATE  asset.copy
12022               SET   call_number = target_cn.id
12023               WHERE call_number = source_cn.id;
12024
12025             -- ... move V holds to the move-target call number
12026             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
12027
12028                 UPDATE  action.hold_request
12029                   SET   target = target_cn.id
12030                   WHERE id = hold.id;
12031
12032                 moved_objects := moved_objects + 1;
12033             END LOOP;
12034
12035         -- ... if not ...
12036         ELSE
12037             -- ... just move the call number to the target record
12038             UPDATE  asset.call_number
12039               SET   record = target_record
12040               WHERE id = source_cn.id;
12041         END IF;
12042
12043         moved_objects := moved_objects + 1;
12044     END LOOP;
12045
12046     -- Find T holds targeting the source record ...
12047     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
12048
12049         -- ... and move them to the target record
12050         UPDATE  action.hold_request
12051           SET   target = target_record
12052           WHERE id = hold.id;
12053
12054         moved_objects := moved_objects + 1;
12055     END LOOP;
12056
12057     -- Find serial records targeting the source record ...
12058     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
12059         -- ... and move them to the target record
12060         UPDATE  serial.record_entry
12061           SET   record = target_record
12062           WHERE id = ser_rec.id;
12063
12064         moved_objects := moved_objects + 1;
12065     END LOOP;
12066
12067     -- Finally, "delete" the source record
12068     DELETE FROM biblio.record_entry WHERE id = source_record;
12069
12070     -- That's all, folks!
12071     RETURN moved_objects;
12072 END;
12073 $func$ LANGUAGE plpgsql;
12074
12075 CREATE OR REPLACE FUNCTION acq.transfer_fund(
12076         old_fund   IN INT,
12077         old_amount IN NUMERIC,     -- in currency of old fund
12078         new_fund   IN INT,
12079         new_amount IN NUMERIC,     -- in currency of new fund
12080         user_id    IN INT,
12081         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
12082         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
12083 ) RETURNS VOID AS $$
12084 /* -------------------------------------------------------------------------------
12085
12086 Function to transfer money from one fund to another.
12087
12088 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12089 negative amount for the old (losing) fund and a positive amount for the new
12090 (gaining) fund.  In some cases there may be more than one such pair of entries
12091 in order to pull the money from different funding sources, or more specifically
12092 from different funding source credits.  For each such pair there is also an
12093 entry in acq.fund_transfer.
12094
12095 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12096 choose a funding source for the transferred money to come from.  This choice
12097 must meet two constraints, so far as possible:
12098
12099 1. The amount transferred from a given funding source must not exceed the
12100 amount allocated to the old fund by the funding source.  To that end we
12101 compare the amount being transferred to the amount allocated.
12102
12103 2. We shouldn't transfer money that has already been spent or encumbered, as
12104 defined by the funding attribution process.  We attribute expenses to the
12105 oldest funding source credits first.  In order to avoid transferring that
12106 attributed money, we reverse the priority, transferring from the newest funding
12107 source credits first.  There can be no guarantee that this approach will
12108 avoid overcommitting a fund, but no other approach can do any better.
12109
12110 In this context the age of a funding source credit is defined by the
12111 deadline_date for credits with deadline_dates, and by the effective_date for
12112 credits without deadline_dates, with the proviso that credits with deadline_dates
12113 are all considered "older" than those without.
12114
12115 ----------
12116
12117 In the signature for this function, there is one last parameter commented out,
12118 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12119 driving the main loop has an OR clause commented out, which references the
12120 funding_source_in parameter.
12121
12122 If these lines are uncommented, this function will allow the user optionally to
12123 restrict a fund transfer to a specified funding source.  If the source
12124 parameter is left NULL, then there will be no such restriction.
12125
12126 ------------------------------------------------------------------------------- */ 
12127 DECLARE
12128         same_currency      BOOLEAN;
12129         currency_ratio     NUMERIC;
12130         old_fund_currency  TEXT;
12131         old_remaining      NUMERIC;  -- in currency of old fund
12132         new_fund_currency  TEXT;
12133         new_fund_active    BOOLEAN;
12134         new_remaining      NUMERIC;  -- in currency of new fund
12135         curr_old_amt       NUMERIC;  -- in currency of old fund
12136         curr_new_amt       NUMERIC;  -- in currency of new fund
12137         source_addition    NUMERIC;  -- in currency of funding source
12138         source_deduction   NUMERIC;  -- in currency of funding source
12139         orig_allocated_amt NUMERIC;  -- in currency of funding source
12140         allocated_amt      NUMERIC;  -- in currency of fund
12141         source             RECORD;
12142 BEGIN
12143         --
12144         -- Sanity checks
12145         --
12146         IF old_fund IS NULL THEN
12147                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12148         END IF;
12149         --
12150         IF old_amount IS NULL THEN
12151                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12152         END IF;
12153         --
12154         -- The new fund and its amount must be both NULL or both not NULL.
12155         --
12156         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12157                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12158         END IF;
12159         --
12160         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12161                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12162         END IF;
12163         --
12164         IF user_id IS NULL THEN
12165                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12166         END IF;
12167         --
12168         -- Initialize the amounts to be transferred, each denominated
12169         -- in the currency of its respective fund.  They will be
12170         -- reduced on each iteration of the loop.
12171         --
12172         old_remaining := old_amount;
12173         new_remaining := new_amount;
12174         --
12175         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12176         --      old_amount, old_fund, new_amount, new_fund;
12177         --
12178         -- Get the currency types of the old and new funds.
12179         --
12180         SELECT
12181                 currency_type
12182         INTO
12183                 old_fund_currency
12184         FROM
12185                 acq.fund
12186         WHERE
12187                 id = old_fund;
12188         --
12189         IF old_fund_currency IS NULL THEN
12190                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12191         END IF;
12192         --
12193         IF new_fund IS NOT NULL THEN
12194                 SELECT
12195                         currency_type,
12196                         active
12197                 INTO
12198                         new_fund_currency,
12199                         new_fund_active
12200                 FROM
12201                         acq.fund
12202                 WHERE
12203                         id = new_fund;
12204                 --
12205                 IF new_fund_currency IS NULL THEN
12206                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12207                 ELSIF NOT new_fund_active THEN
12208                         --
12209                         -- No point in putting money into a fund from whence you can't spend it
12210                         --
12211                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12212                 END IF;
12213                 --
12214                 IF new_amount = old_amount THEN
12215                         same_currency := true;
12216                         currency_ratio := 1;
12217                 ELSE
12218                         --
12219                         -- We'll have to translate currency between funds.  We presume that
12220                         -- the calling code has already applied an appropriate exchange rate,
12221                         -- so we'll apply the same conversion to each sub-transfer.
12222                         --
12223                         same_currency := false;
12224                         currency_ratio := new_amount / old_amount;
12225                 END IF;
12226         END IF;
12227         --
12228         -- Identify the funding source(s) from which we want to transfer the money.
12229         -- The principle is that we want to transfer the newest money first, because
12230         -- we spend the oldest money first.  The priority for spending is defined
12231         -- by a sort of the view acq.ordered_funding_source_credit.
12232         --
12233         FOR source in
12234                 SELECT
12235                         ofsc.id,
12236                         ofsc.funding_source,
12237                         ofsc.amount,
12238                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12239                                 AS converted_amt,
12240                         fs.currency_type
12241                 FROM
12242                         acq.ordered_funding_source_credit AS ofsc,
12243                         acq.funding_source fs
12244                 WHERE
12245                         ofsc.funding_source = fs.id
12246                         and ofsc.funding_source IN
12247                         (
12248                                 SELECT funding_source
12249                                 FROM acq.fund_allocation
12250                                 WHERE fund = old_fund
12251                         )
12252                         -- and
12253                         -- (
12254                         --      ofsc.funding_source = funding_source_in
12255                         --      OR funding_source_in IS NULL
12256                         -- )
12257                 ORDER BY
12258                         ofsc.sort_priority desc,
12259                         ofsc.sort_date desc,
12260                         ofsc.id desc
12261         LOOP
12262                 --
12263                 -- Determine how much money the old fund got from this funding source,
12264                 -- denominated in the currency types of the source and of the fund.
12265                 -- This result may reflect transfers from previous iterations.
12266                 --
12267                 SELECT
12268                         COALESCE( sum( amount ), 0 ),
12269                         COALESCE( sum( amount )
12270                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12271                 INTO
12272                         orig_allocated_amt,     -- in currency of the source
12273                         allocated_amt           -- in currency of the old fund
12274                 FROM
12275                         acq.fund_allocation
12276                 WHERE
12277                         fund = old_fund
12278                         and funding_source = source.funding_source;
12279                 --      
12280                 -- Determine how much to transfer from this credit, in the currency
12281                 -- of the fund.   Begin with the amount remaining to be attributed:
12282                 --
12283                 curr_old_amt := old_remaining;
12284                 --
12285                 -- Can't attribute more than was allocated from the fund:
12286                 --
12287                 IF curr_old_amt > allocated_amt THEN
12288                         curr_old_amt := allocated_amt;
12289                 END IF;
12290                 --
12291                 -- Can't attribute more than the amount of the current credit:
12292                 --
12293                 IF curr_old_amt > source.converted_amt THEN
12294                         curr_old_amt := source.converted_amt;
12295                 END IF;
12296                 --
12297                 curr_old_amt := trunc( curr_old_amt, 2 );
12298                 --
12299                 old_remaining := old_remaining - curr_old_amt;
12300                 --
12301                 -- Determine the amount to be deducted, if any,
12302                 -- from the old allocation.
12303                 --
12304                 IF old_remaining > 0 THEN
12305                         --
12306                         -- In this case we're using the whole allocation, so use that
12307                         -- amount directly instead of applying a currency translation
12308                         -- and thereby inviting round-off errors.
12309                         --
12310                         source_deduction := - orig_allocated_amt;
12311                 ELSE 
12312                         source_deduction := trunc(
12313                                 ( - curr_old_amt ) *
12314                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12315                                 2 );
12316                 END IF;
12317                 --
12318                 IF source_deduction <> 0 THEN
12319                         --
12320                         -- Insert negative allocation for old fund in fund_allocation,
12321                         -- converted into the currency of the funding source
12322                         --
12323                         INSERT INTO acq.fund_allocation (
12324                                 funding_source,
12325                                 fund,
12326                                 amount,
12327                                 allocator,
12328                                 note
12329                         ) VALUES (
12330                                 source.funding_source,
12331                                 old_fund,
12332                                 source_deduction,
12333                                 user_id,
12334                                 'Transfer to fund ' || new_fund
12335                         );
12336                 END IF;
12337                 --
12338                 IF new_fund IS NOT NULL THEN
12339                         --
12340                         -- Determine how much to add to the new fund, in
12341                         -- its currency, and how much remains to be added:
12342                         --
12343                         IF same_currency THEN
12344                                 curr_new_amt := curr_old_amt;
12345                         ELSE
12346                                 IF old_remaining = 0 THEN
12347                                         --
12348                                         -- This is the last iteration, so nothing should be left
12349                                         --
12350                                         curr_new_amt := new_remaining;
12351                                         new_remaining := 0;
12352                                 ELSE
12353                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12354                                         new_remaining := new_remaining - curr_new_amt;
12355                                 END IF;
12356                         END IF;
12357                         --
12358                         -- Determine how much to add, if any,
12359                         -- to the new fund's allocation.
12360                         --
12361                         IF old_remaining > 0 THEN
12362                                 --
12363                                 -- In this case we're using the whole allocation, so use that amount
12364                                 -- amount directly instead of applying a currency translation and
12365                                 -- thereby inviting round-off errors.
12366                                 --
12367                                 source_addition := orig_allocated_amt;
12368                         ELSIF source.currency_type = old_fund_currency THEN
12369                                 --
12370                                 -- In this case we don't need a round trip currency translation,
12371                                 -- thereby inviting round-off errors:
12372                                 --
12373                                 source_addition := curr_old_amt;
12374                         ELSE 
12375                                 source_addition := trunc(
12376                                         curr_new_amt *
12377                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12378                                         2 );
12379                         END IF;
12380                         --
12381                         IF source_addition <> 0 THEN
12382                                 --
12383                                 -- Insert positive allocation for new fund in fund_allocation,
12384                                 -- converted to the currency of the founding source
12385                                 --
12386                                 INSERT INTO acq.fund_allocation (
12387                                         funding_source,
12388                                         fund,
12389                                         amount,
12390                                         allocator,
12391                                         note
12392                                 ) VALUES (
12393                                         source.funding_source,
12394                                         new_fund,
12395                                         source_addition,
12396                                         user_id,
12397                                         'Transfer from fund ' || old_fund
12398                                 );
12399                         END IF;
12400                 END IF;
12401                 --
12402                 IF trunc( curr_old_amt, 2 ) <> 0
12403                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12404                         --
12405                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12406                         --
12407                         INSERT INTO acq.fund_transfer (
12408                                 src_fund,
12409                                 src_amount,
12410                                 dest_fund,
12411                                 dest_amount,
12412                                 transfer_user,
12413                                 note,
12414                                 funding_source_credit
12415                         ) VALUES (
12416                                 old_fund,
12417                                 trunc( curr_old_amt, 2 ),
12418                                 new_fund,
12419                                 trunc( curr_new_amt, 2 ),
12420                                 user_id,
12421                                 xfer_note,
12422                                 source.id
12423                         );
12424                 END IF;
12425                 --
12426                 if old_remaining <= 0 THEN
12427                         EXIT;                   -- Nothing more to be transferred
12428                 END IF;
12429         END LOOP;
12430 END;
12431 $$ LANGUAGE plpgsql;
12432
12433 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
12434         old_year INTEGER,
12435         user_id INTEGER,
12436         org_unit_id INTEGER
12437 ) RETURNS VOID AS $$
12438 DECLARE
12439 --
12440 new_id      INT;
12441 old_fund    RECORD;
12442 org_found   BOOLEAN;
12443 --
12444 BEGIN
12445         --
12446         -- Sanity checks
12447         --
12448         IF old_year IS NULL THEN
12449                 RAISE EXCEPTION 'Input year argument is NULL';
12450         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12451                 RAISE EXCEPTION 'Input year is out of range';
12452         END IF;
12453         --
12454         IF user_id IS NULL THEN
12455                 RAISE EXCEPTION 'Input user id argument is NULL';
12456         END IF;
12457         --
12458         IF org_unit_id IS NULL THEN
12459                 RAISE EXCEPTION 'Org unit id argument is NULL';
12460         ELSE
12461                 SELECT TRUE INTO org_found
12462                 FROM actor.org_unit
12463                 WHERE id = org_unit_id;
12464                 --
12465                 IF org_found IS NULL THEN
12466                         RAISE EXCEPTION 'Org unit id is invalid';
12467                 END IF;
12468         END IF;
12469         --
12470         -- Loop over the applicable funds
12471         --
12472         FOR old_fund in SELECT * FROM acq.fund
12473         WHERE
12474                 year = old_year
12475                 AND propagate
12476                 AND org = org_unit_id
12477         LOOP
12478                 BEGIN
12479                         INSERT INTO acq.fund (
12480                                 org,
12481                                 name,
12482                                 year,
12483                                 currency_type,
12484                                 code,
12485                                 rollover,
12486                                 propagate,
12487                                 balance_warning_percent,
12488                                 balance_stop_percent
12489                         ) VALUES (
12490                                 old_fund.org,
12491                                 old_fund.name,
12492                                 old_year + 1,
12493                                 old_fund.currency_type,
12494                                 old_fund.code,
12495                                 old_fund.rollover,
12496                                 true,
12497                                 old_fund.balance_warning_percent,
12498                                 old_fund.balance_stop_percent
12499                         )
12500                         RETURNING id INTO new_id;
12501                 EXCEPTION
12502                         WHEN unique_violation THEN
12503                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12504                                 CONTINUE;
12505                 END;
12506                 --RAISE NOTICE 'Propagating fund % to fund %',
12507                 --      old_fund.code, new_id;
12508         END LOOP;
12509 END;
12510 $$ LANGUAGE plpgsql;
12511
12512 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12513         old_year INTEGER,
12514         user_id INTEGER,
12515         org_unit_id INTEGER
12516 ) RETURNS VOID AS $$
12517 DECLARE
12518 --
12519 new_id      INT;
12520 old_fund    RECORD;
12521 org_found   BOOLEAN;
12522 --
12523 BEGIN
12524         --
12525         -- Sanity checks
12526         --
12527         IF old_year IS NULL THEN
12528                 RAISE EXCEPTION 'Input year argument is NULL';
12529         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12530                 RAISE EXCEPTION 'Input year is out of range';
12531         END IF;
12532         --
12533         IF user_id IS NULL THEN
12534                 RAISE EXCEPTION 'Input user id argument is NULL';
12535         END IF;
12536         --
12537         IF org_unit_id IS NULL THEN
12538                 RAISE EXCEPTION 'Org unit id argument is NULL';
12539         ELSE
12540                 SELECT TRUE INTO org_found
12541                 FROM actor.org_unit
12542                 WHERE id = org_unit_id;
12543                 --
12544                 IF org_found IS NULL THEN
12545                         RAISE EXCEPTION 'Org unit id is invalid';
12546                 END IF;
12547         END IF;
12548         --
12549         -- Loop over the applicable funds
12550         --
12551         FOR old_fund in SELECT * FROM acq.fund
12552         WHERE
12553                 year = old_year
12554                 AND propagate
12555                 AND org in (
12556                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12557                 )
12558         LOOP
12559                 BEGIN
12560                         INSERT INTO acq.fund (
12561                                 org,
12562                                 name,
12563                                 year,
12564                                 currency_type,
12565                                 code,
12566                                 rollover,
12567                                 propagate,
12568                                 balance_warning_percent,
12569                                 balance_stop_percent
12570                         ) VALUES (
12571                                 old_fund.org,
12572                                 old_fund.name,
12573                                 old_year + 1,
12574                                 old_fund.currency_type,
12575                                 old_fund.code,
12576                                 old_fund.rollover,
12577                                 true,
12578                                 old_fund.balance_warning_percent,
12579                                 old_fund.balance_stop_percent
12580                         )
12581                         RETURNING id INTO new_id;
12582                 EXCEPTION
12583                         WHEN unique_violation THEN
12584                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12585                                 CONTINUE;
12586                 END;
12587                 --RAISE NOTICE 'Propagating fund % to fund %',
12588                 --      old_fund.code, new_id;
12589         END LOOP;
12590 END;
12591 $$ LANGUAGE plpgsql;
12592
12593 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12594         old_year INTEGER,
12595         user_id INTEGER,
12596         org_unit_id INTEGER
12597 ) RETURNS VOID AS $$
12598 DECLARE
12599 --
12600 new_fund    INT;
12601 new_year    INT := old_year + 1;
12602 org_found   BOOL;
12603 xfer_amount NUMERIC;
12604 roll_fund   RECORD;
12605 deb         RECORD;
12606 detail      RECORD;
12607 --
12608 BEGIN
12609         --
12610         -- Sanity checks
12611         --
12612         IF old_year IS NULL THEN
12613                 RAISE EXCEPTION 'Input year argument is NULL';
12614     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12615         RAISE EXCEPTION 'Input year is out of range';
12616         END IF;
12617         --
12618         IF user_id IS NULL THEN
12619                 RAISE EXCEPTION 'Input user id argument is NULL';
12620         END IF;
12621         --
12622         IF org_unit_id IS NULL THEN
12623                 RAISE EXCEPTION 'Org unit id argument is NULL';
12624         ELSE
12625                 --
12626                 -- Validate the org unit
12627                 --
12628                 SELECT TRUE
12629                 INTO org_found
12630                 FROM actor.org_unit
12631                 WHERE id = org_unit_id;
12632                 --
12633                 IF org_found IS NULL THEN
12634                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12635                 END IF;
12636         END IF;
12637         --
12638         -- Loop over the propagable funds to identify the details
12639         -- from the old fund plus the id of the new one, if it exists.
12640         --
12641         FOR roll_fund in
12642         SELECT
12643             oldf.id AS old_fund,
12644             oldf.org,
12645             oldf.name,
12646             oldf.currency_type,
12647             oldf.code,
12648                 oldf.rollover,
12649             newf.id AS new_fund_id
12650         FROM
12651         acq.fund AS oldf
12652         LEFT JOIN acq.fund AS newf
12653                 ON ( oldf.code = newf.code )
12654         WHERE
12655                     oldf.org = org_unit_id
12656                 and oldf.year = old_year
12657                 and oldf.propagate
12658         and newf.year = new_year
12659         LOOP
12660                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12661                 --
12662                 IF roll_fund.new_fund_id IS NULL THEN
12663                         --
12664                         -- The old fund hasn't been propagated yet.  Propagate it now.
12665                         --
12666                         INSERT INTO acq.fund (
12667                                 org,
12668                                 name,
12669                                 year,
12670                                 currency_type,
12671                                 code,
12672                                 rollover,
12673                                 propagate,
12674                                 balance_warning_percent,
12675                                 balance_stop_percent
12676                         ) VALUES (
12677                                 roll_fund.org,
12678                                 roll_fund.name,
12679                                 new_year,
12680                                 roll_fund.currency_type,
12681                                 roll_fund.code,
12682                                 true,
12683                                 true,
12684                                 roll_fund.balance_warning_percent,
12685                                 roll_fund.balance_stop_percent
12686                         )
12687                         RETURNING id INTO new_fund;
12688                 ELSE
12689                         new_fund = roll_fund.new_fund_id;
12690                 END IF;
12691                 --
12692                 -- Determine the amount to transfer
12693                 --
12694                 SELECT amount
12695                 INTO xfer_amount
12696                 FROM acq.fund_spent_balance
12697                 WHERE fund = roll_fund.old_fund;
12698                 --
12699                 IF xfer_amount <> 0 THEN
12700                         IF roll_fund.rollover THEN
12701                                 --
12702                                 -- Transfer balance from old fund to new
12703                                 --
12704                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12705                                 --
12706                                 PERFORM acq.transfer_fund(
12707                                         roll_fund.old_fund,
12708                                         xfer_amount,
12709                                         new_fund,
12710                                         xfer_amount,
12711                                         user_id,
12712                                         'Rollover'
12713                                 );
12714                         ELSE
12715                                 --
12716                                 -- Transfer balance from old fund to the void
12717                                 --
12718                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12719                                 --
12720                                 PERFORM acq.transfer_fund(
12721                                         roll_fund.old_fund,
12722                                         xfer_amount,
12723                                         NULL,
12724                                         NULL,
12725                                         user_id,
12726                                         'Rollover'
12727                                 );
12728                         END IF;
12729                 END IF;
12730                 --
12731                 IF roll_fund.rollover THEN
12732                         --
12733                         -- Move any lineitems from the old fund to the new one
12734                         -- where the associated debit is an encumbrance.
12735                         --
12736                         -- Any other tables tying expenditure details to funds should
12737                         -- receive similar treatment.  At this writing there are none.
12738                         --
12739                         UPDATE acq.lineitem_detail
12740                         SET fund = new_fund
12741                         WHERE
12742                         fund = roll_fund.old_fund -- this condition may be redundant
12743                         AND fund_debit in
12744                         (
12745                                 SELECT id
12746                                 FROM acq.fund_debit
12747                                 WHERE
12748                                 fund = roll_fund.old_fund
12749                                 AND encumbrance
12750                         );
12751                         --
12752                         -- Move encumbrance debits from the old fund to the new fund
12753                         --
12754                         UPDATE acq.fund_debit
12755                         SET fund = new_fund
12756                         wHERE
12757                                 fund = roll_fund.old_fund
12758                                 AND encumbrance;
12759                 END IF;
12760                 --
12761                 -- Mark old fund as inactive, now that we've closed it
12762                 --
12763                 UPDATE acq.fund
12764                 SET active = FALSE
12765                 WHERE id = roll_fund.old_fund;
12766         END LOOP;
12767 END;
12768 $$ LANGUAGE plpgsql;
12769
12770 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12771         old_year INTEGER,
12772         user_id INTEGER,
12773         org_unit_id INTEGER
12774 ) RETURNS VOID AS $$
12775 DECLARE
12776 --
12777 new_fund    INT;
12778 new_year    INT := old_year + 1;
12779 org_found   BOOL;
12780 xfer_amount NUMERIC;
12781 roll_fund   RECORD;
12782 deb         RECORD;
12783 detail      RECORD;
12784 --
12785 BEGIN
12786         --
12787         -- Sanity checks
12788         --
12789         IF old_year IS NULL THEN
12790                 RAISE EXCEPTION 'Input year argument is NULL';
12791     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12792         RAISE EXCEPTION 'Input year is out of range';
12793         END IF;
12794         --
12795         IF user_id IS NULL THEN
12796                 RAISE EXCEPTION 'Input user id argument is NULL';
12797         END IF;
12798         --
12799         IF org_unit_id IS NULL THEN
12800                 RAISE EXCEPTION 'Org unit id argument is NULL';
12801         ELSE
12802                 --
12803                 -- Validate the org unit
12804                 --
12805                 SELECT TRUE
12806                 INTO org_found
12807                 FROM actor.org_unit
12808                 WHERE id = org_unit_id;
12809                 --
12810                 IF org_found IS NULL THEN
12811                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12812                 END IF;
12813         END IF;
12814         --
12815         -- Loop over the propagable funds to identify the details
12816         -- from the old fund plus the id of the new one, if it exists.
12817         --
12818         FOR roll_fund in
12819         SELECT
12820             oldf.id AS old_fund,
12821             oldf.org,
12822             oldf.name,
12823             oldf.currency_type,
12824             oldf.code,
12825                 oldf.rollover,
12826             newf.id AS new_fund_id
12827         FROM
12828         acq.fund AS oldf
12829         LEFT JOIN acq.fund AS newf
12830                 ON ( oldf.code = newf.code )
12831         WHERE
12832                     oldf.year = old_year
12833                 AND oldf.propagate
12834         AND newf.year = new_year
12835                 AND oldf.org in (
12836                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12837                 )
12838         LOOP
12839                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12840                 --
12841                 IF roll_fund.new_fund_id IS NULL THEN
12842                         --
12843                         -- The old fund hasn't been propagated yet.  Propagate it now.
12844                         --
12845                         INSERT INTO acq.fund (
12846                                 org,
12847                                 name,
12848                                 year,
12849                                 currency_type,
12850                                 code,
12851                                 rollover,
12852                                 propagate,
12853                                 balance_warning_percent,
12854                                 balance_stop_percent
12855                         ) VALUES (
12856                                 roll_fund.org,
12857                                 roll_fund.name,
12858                                 new_year,
12859                                 roll_fund.currency_type,
12860                                 roll_fund.code,
12861                                 true,
12862                                 true,
12863                                 roll_fund.balance_warning_percent,
12864                                 roll_fund.balance_stop_percent
12865                         )
12866                         RETURNING id INTO new_fund;
12867                 ELSE
12868                         new_fund = roll_fund.new_fund_id;
12869                 END IF;
12870                 --
12871                 -- Determine the amount to transfer
12872                 --
12873                 SELECT amount
12874                 INTO xfer_amount
12875                 FROM acq.fund_spent_balance
12876                 WHERE fund = roll_fund.old_fund;
12877                 --
12878                 IF xfer_amount <> 0 THEN
12879                         IF roll_fund.rollover THEN
12880                                 --
12881                                 -- Transfer balance from old fund to new
12882                                 --
12883                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12884                                 --
12885                                 PERFORM acq.transfer_fund(
12886                                         roll_fund.old_fund,
12887                                         xfer_amount,
12888                                         new_fund,
12889                                         xfer_amount,
12890                                         user_id,
12891                                         'Rollover'
12892                                 );
12893                         ELSE
12894                                 --
12895                                 -- Transfer balance from old fund to the void
12896                                 --
12897                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12898                                 --
12899                                 PERFORM acq.transfer_fund(
12900                                         roll_fund.old_fund,
12901                                         xfer_amount,
12902                                         NULL,
12903                                         NULL,
12904                                         user_id,
12905                                         'Rollover'
12906                                 );
12907                         END IF;
12908                 END IF;
12909                 --
12910                 IF roll_fund.rollover THEN
12911                         --
12912                         -- Move any lineitems from the old fund to the new one
12913                         -- where the associated debit is an encumbrance.
12914                         --
12915                         -- Any other tables tying expenditure details to funds should
12916                         -- receive similar treatment.  At this writing there are none.
12917                         --
12918                         UPDATE acq.lineitem_detail
12919                         SET fund = new_fund
12920                         WHERE
12921                         fund = roll_fund.old_fund -- this condition may be redundant
12922                         AND fund_debit in
12923                         (
12924                                 SELECT id
12925                                 FROM acq.fund_debit
12926                                 WHERE
12927                                 fund = roll_fund.old_fund
12928                                 AND encumbrance
12929                         );
12930                         --
12931                         -- Move encumbrance debits from the old fund to the new fund
12932                         --
12933                         UPDATE acq.fund_debit
12934                         SET fund = new_fund
12935                         wHERE
12936                                 fund = roll_fund.old_fund
12937                                 AND encumbrance;
12938                 END IF;
12939                 --
12940                 -- Mark old fund as inactive, now that we've closed it
12941                 --
12942                 UPDATE acq.fund
12943                 SET active = FALSE
12944                 WHERE id = roll_fund.old_fund;
12945         END LOOP;
12946 END;
12947 $$ LANGUAGE plpgsql;
12948
12949 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
12950     SELECT regexp_replace($1, ',', '', 'g');
12951 $$ LANGUAGE SQL STRICT IMMUTABLE;
12952
12953 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
12954     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
12955 $$ LANGUAGE SQL STRICT IMMUTABLE;
12956
12957 CREATE TABLE acq.distribution_formula_application (
12958     id BIGSERIAL PRIMARY KEY,
12959     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
12960     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
12961     formula INT NOT NULL
12962         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
12963     lineitem INT NOT NULL
12964         REFERENCES acq.lineitem( id )
12965                 ON DELETE CASCADE
12966                 DEFERRABLE INITIALLY DEFERRED
12967 );
12968
12969 CREATE INDEX acqdfa_df_idx
12970     ON acq.distribution_formula_application(formula);
12971 CREATE INDEX acqdfa_li_idx
12972     ON acq.distribution_formula_application(lineitem);
12973 CREATE INDEX acqdfa_creator_idx
12974     ON acq.distribution_formula_application(creator);
12975
12976 CREATE TABLE acq.user_request_type (
12977     id      SERIAL  PRIMARY KEY,
12978     label   TEXT    NOT NULL UNIQUE -- i18n-ize
12979 );
12980
12981 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
12982 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
12983 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
12984 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
12985 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
12986
12987 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
12988
12989 CREATE TABLE acq.cancel_reason (
12990         id            SERIAL            PRIMARY KEY,
12991         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
12992                                         DEFERRABLE INITIALLY DEFERRED,
12993         label         TEXT              NOT NULL,
12994         description   TEXT              NOT NULL,
12995         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
12996         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
12997 );
12998
12999 -- Reserve ids 1-999 for stock reasons
13000 -- Reserve ids 1000-1999 for EDI reasons
13001 -- 2000+ are available for staff to create
13002
13003 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
13004
13005 CREATE TABLE acq.user_request (
13006     id                  SERIAL  PRIMARY KEY,
13007     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
13008     hold                BOOL    NOT NULL DEFAULT TRUE,
13009
13010     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
13011     holdable_formats    TEXT,           -- nullable, for use in hold creation
13012     phone_notify        TEXT,
13013     email_notify        BOOL    NOT NULL DEFAULT TRUE,
13014     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
13015     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
13016     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
13017     need_before         TIMESTAMPTZ,    -- don't create holds after this
13018     max_fee             TEXT,
13019
13020     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
13021     isxn                TEXT,
13022     title               TEXT,
13023     volume              TEXT,
13024     author              TEXT,
13025     article_title       TEXT,
13026     article_pages       TEXT,
13027     publisher           TEXT,
13028     location            TEXT,
13029     pubdate             TEXT,
13030     mentioned           TEXT,
13031     other_info          TEXT,
13032         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
13033                                              DEFERRABLE INITIALLY DEFERRED
13034 );
13035
13036 CREATE TABLE acq.lineitem_alert_text (
13037         id               SERIAL         PRIMARY KEY,
13038         code             TEXT           NOT NULL,
13039         description      TEXT,
13040         owning_lib       INT            NOT NULL
13041                                         REFERENCES actor.org_unit(id)
13042                                         DEFERRABLE INITIALLY DEFERRED,
13043         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
13044 );
13045
13046 ALTER TABLE acq.lineitem_note
13047         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
13048                                          DEFERRABLE INITIALLY DEFERRED;
13049
13050 -- add ON DELETE CASCADE clause
13051
13052 ALTER TABLE acq.lineitem_note
13053         DROP CONSTRAINT lineitem_note_lineitem_fkey;
13054
13055 ALTER TABLE acq.lineitem_note
13056         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13057                 ON DELETE CASCADE
13058                 DEFERRABLE INITIALLY DEFERRED;
13059
13060 ALTER TABLE acq.lineitem_note
13061         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13062
13063 CREATE TABLE acq.invoice_method (
13064     code    TEXT    PRIMARY KEY,
13065     name    TEXT    NOT NULL -- i18n-ize
13066 );
13067 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
13068 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
13069
13070 CREATE TABLE acq.invoice_payment_method (
13071         code      TEXT     PRIMARY KEY,
13072         name      TEXT     NOT NULL
13073 );
13074
13075 CREATE TABLE acq.invoice (
13076     id             SERIAL      PRIMARY KEY,
13077     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
13078     provider       INT         NOT NULL REFERENCES acq.provider (id),
13079     shipper        INT         NOT NULL REFERENCES acq.provider (id),
13080     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13081     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
13082     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
13083     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
13084         payment_auth   TEXT,
13085         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
13086                                    DEFERRABLE INITIALLY DEFERRED,
13087         note           TEXT,
13088     complete       BOOL        NOT NULL DEFAULT FALSE,
13089     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13090 );
13091
13092 CREATE TABLE acq.invoice_entry (
13093     id              SERIAL      PRIMARY KEY,
13094     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13095     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13096     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13097     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13098     phys_item_count INT, -- and how many did staff count
13099     note            TEXT,
13100     billed_per_item BOOL,
13101     cost_billed     NUMERIC(8,2),
13102     actual_cost     NUMERIC(8,2),
13103         amount_paid     NUMERIC (8,2)
13104 );
13105
13106 CREATE TABLE acq.invoice_item_type (
13107     code    TEXT    PRIMARY KEY,
13108     name    TEXT    NOT NULL, -- i18n-ize
13109         prorate BOOL    NOT NULL DEFAULT FALSE
13110 );
13111
13112 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13113 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13114 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13115 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13116 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13117 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13118
13119 CREATE TABLE acq.po_item (
13120         id              SERIAL      PRIMARY KEY,
13121         purchase_order  INT         REFERENCES acq.purchase_order (id)
13122                                     ON UPDATE CASCADE ON DELETE SET NULL
13123                                     DEFERRABLE INITIALLY DEFERRED,
13124         fund_debit      INT         REFERENCES acq.fund_debit (id)
13125                                     DEFERRABLE INITIALLY DEFERRED,
13126         inv_item_type   TEXT        NOT NULL
13127                                     REFERENCES acq.invoice_item_type (code)
13128                                     DEFERRABLE INITIALLY DEFERRED,
13129         title           TEXT,
13130         author          TEXT,
13131         note            TEXT,
13132         estimated_cost  NUMERIC(8,2),
13133         fund            INT         REFERENCES acq.fund (id)
13134                                     DEFERRABLE INITIALLY DEFERRED,
13135         target          BIGINT
13136 );
13137
13138 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13139     id              SERIAL      PRIMARY KEY,
13140     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13141     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13142     fund_debit      INT         REFERENCES acq.fund_debit (id),
13143     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13144     title           TEXT,
13145     author          TEXT,
13146     note            TEXT,
13147     cost_billed     NUMERIC(8,2),
13148     actual_cost     NUMERIC(8,2),
13149     fund            INT         REFERENCES acq.fund (id)
13150                                 DEFERRABLE INITIALLY DEFERRED,
13151     amount_paid     NUMERIC (8,2),
13152     po_item         INT         REFERENCES acq.po_item (id)
13153                                 DEFERRABLE INITIALLY DEFERRED,
13154     target          BIGINT
13155 );
13156
13157 CREATE TABLE acq.edi_message (
13158     id               SERIAL          PRIMARY KEY,
13159     account          INTEGER         REFERENCES acq.edi_account(id)
13160                                      DEFERRABLE INITIALLY DEFERRED,
13161     remote_file      TEXT,
13162     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13163     translate_time   TIMESTAMPTZ,
13164     process_time     TIMESTAMPTZ,
13165     error_time       TIMESTAMPTZ,
13166     status           TEXT            NOT NULL DEFAULT 'new'
13167                                      CONSTRAINT status_value CHECK
13168                                      ( status IN (
13169                                         'new',          -- needs to be translated
13170                                         'translated',   -- needs to be processed
13171                                         'trans_error',  -- error in translation step
13172                                         'processed',    -- needs to have remote_file deleted
13173                                         'proc_error',   -- error in processing step
13174                                         'delete_error', -- error in deletion
13175                                         'retry',        -- need to retry
13176                                         'complete'      -- done
13177                                      )),
13178     edi              TEXT,
13179     jedi             TEXT,
13180     error            TEXT,
13181     purchase_order   INT             REFERENCES acq.purchase_order
13182                                      DEFERRABLE INITIALLY DEFERRED,
13183     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13184                                      CHECK ( message_type IN (
13185                                         'ORDERS',
13186                                         'ORDRSP',
13187                                         'INVOIC',
13188                                         'OSTENQ',
13189                                         'OSTRPT'
13190                                      ))
13191 );
13192
13193 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13194
13195 ALTER TABLE acq.provider_address
13196         ADD COLUMN fax_phone TEXT;
13197
13198 ALTER TABLE acq.provider_contact_address
13199         ADD COLUMN fax_phone TEXT;
13200
13201 CREATE TABLE acq.provider_note (
13202     id      SERIAL              PRIMARY KEY,
13203     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13204     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13205     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13206     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13207     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13208     value       TEXT            NOT NULL
13209 );
13210 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13211 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13212 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13213
13214 -- For each fund: the total allocation from all sources, in the
13215 -- currency of the fund (or 0 if there are no allocations)
13216
13217 CREATE VIEW acq.all_fund_allocation_total AS
13218 SELECT
13219     f.id AS fund,
13220     COALESCE( SUM( a.amount * acq.exchange_ratio(
13221         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13222     AS amount
13223 FROM
13224     acq.fund f
13225         LEFT JOIN acq.fund_allocation a
13226             ON a.fund = f.id
13227         LEFT JOIN acq.funding_source s
13228             ON a.funding_source = s.id
13229 GROUP BY
13230     f.id;
13231
13232 -- For every fund: the total encumbrances (or 0 if none),
13233 -- in the currency of the fund.
13234
13235 CREATE VIEW acq.all_fund_encumbrance_total AS
13236 SELECT
13237         f.id AS fund,
13238         COALESCE( encumb.amount, 0 ) AS amount
13239 FROM
13240         acq.fund AS f
13241                 LEFT JOIN (
13242                         SELECT
13243                                 fund,
13244                                 sum( amount ) AS amount
13245                         FROM
13246                                 acq.fund_debit
13247                         WHERE
13248                                 encumbrance
13249                         GROUP BY fund
13250                 ) AS encumb
13251                         ON f.id = encumb.fund;
13252
13253 -- For every fund: the total spent (or 0 if none),
13254 -- in the currency of the fund.
13255
13256 CREATE VIEW acq.all_fund_spent_total AS
13257 SELECT
13258     f.id AS fund,
13259     COALESCE( spent.amount, 0 ) AS amount
13260 FROM
13261     acq.fund AS f
13262         LEFT JOIN (
13263             SELECT
13264                 fund,
13265                 sum( amount ) AS amount
13266             FROM
13267                 acq.fund_debit
13268             WHERE
13269                 NOT encumbrance
13270             GROUP BY fund
13271         ) AS spent
13272             ON f.id = spent.fund;
13273
13274 -- For each fund: the amount not yet spent, in the currency
13275 -- of the fund.  May include encumbrances.
13276
13277 CREATE VIEW acq.all_fund_spent_balance AS
13278 SELECT
13279         c.fund,
13280         c.amount - d.amount AS amount
13281 FROM acq.all_fund_allocation_total c
13282     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13283
13284 -- For each fund: the amount neither spent nor encumbered,
13285 -- in the currency of the fund
13286
13287 CREATE VIEW acq.all_fund_combined_balance AS
13288 SELECT
13289      a.fund,
13290      a.amount - COALESCE( c.amount, 0 ) AS amount
13291 FROM
13292      acq.all_fund_allocation_total a
13293         LEFT OUTER JOIN (
13294             SELECT
13295                 fund,
13296                 SUM( amount ) AS amount
13297             FROM
13298                 acq.fund_debit
13299             GROUP BY
13300                 fund
13301         ) AS c USING ( fund );
13302
13303 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 $$
13304 DECLARE
13305         suffix TEXT;
13306         bucket_row RECORD;
13307         picklist_row RECORD;
13308         queue_row RECORD;
13309         folder_row RECORD;
13310 BEGIN
13311
13312     -- do some initial cleanup 
13313     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13314     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13315     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13316
13317     -- actor.*
13318     IF del_cards THEN
13319         DELETE FROM actor.card where usr = src_usr;
13320     ELSE
13321         IF deactivate_cards THEN
13322             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13323         END IF;
13324         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13325     END IF;
13326
13327
13328     IF del_addrs THEN
13329         DELETE FROM actor.usr_address WHERE usr = src_usr;
13330     ELSE
13331         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13332     END IF;
13333
13334     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13335     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13336     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13337     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13338     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13339
13340     -- permission.*
13341     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13342     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13343     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13344     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13345
13346
13347     -- container.*
13348         
13349         -- For each *_bucket table: transfer every bucket belonging to src_usr
13350         -- into the custody of dest_usr.
13351         --
13352         -- In order to avoid colliding with an existing bucket owned by
13353         -- the destination user, append the source user's id (in parenthesese)
13354         -- to the name.  If you still get a collision, add successive
13355         -- spaces to the name and keep trying until you succeed.
13356         --
13357         FOR bucket_row in
13358                 SELECT id, name
13359                 FROM   container.biblio_record_entry_bucket
13360                 WHERE  owner = src_usr
13361         LOOP
13362                 suffix := ' (' || src_usr || ')';
13363                 LOOP
13364                         BEGIN
13365                                 UPDATE  container.biblio_record_entry_bucket
13366                                 SET     owner = dest_usr, name = name || suffix
13367                                 WHERE   id = bucket_row.id;
13368                         EXCEPTION WHEN unique_violation THEN
13369                                 suffix := suffix || ' ';
13370                                 CONTINUE;
13371                         END;
13372                         EXIT;
13373                 END LOOP;
13374         END LOOP;
13375
13376         FOR bucket_row in
13377                 SELECT id, name
13378                 FROM   container.call_number_bucket
13379                 WHERE  owner = src_usr
13380         LOOP
13381                 suffix := ' (' || src_usr || ')';
13382                 LOOP
13383                         BEGIN
13384                                 UPDATE  container.call_number_bucket
13385                                 SET     owner = dest_usr, name = name || suffix
13386                                 WHERE   id = bucket_row.id;
13387                         EXCEPTION WHEN unique_violation THEN
13388                                 suffix := suffix || ' ';
13389                                 CONTINUE;
13390                         END;
13391                         EXIT;
13392                 END LOOP;
13393         END LOOP;
13394
13395         FOR bucket_row in
13396                 SELECT id, name
13397                 FROM   container.copy_bucket
13398                 WHERE  owner = src_usr
13399         LOOP
13400                 suffix := ' (' || src_usr || ')';
13401                 LOOP
13402                         BEGIN
13403                                 UPDATE  container.copy_bucket
13404                                 SET     owner = dest_usr, name = name || suffix
13405                                 WHERE   id = bucket_row.id;
13406                         EXCEPTION WHEN unique_violation THEN
13407                                 suffix := suffix || ' ';
13408                                 CONTINUE;
13409                         END;
13410                         EXIT;
13411                 END LOOP;
13412         END LOOP;
13413
13414         FOR bucket_row in
13415                 SELECT id, name
13416                 FROM   container.user_bucket
13417                 WHERE  owner = src_usr
13418         LOOP
13419                 suffix := ' (' || src_usr || ')';
13420                 LOOP
13421                         BEGIN
13422                                 UPDATE  container.user_bucket
13423                                 SET     owner = dest_usr, name = name || suffix
13424                                 WHERE   id = bucket_row.id;
13425                         EXCEPTION WHEN unique_violation THEN
13426                                 suffix := suffix || ' ';
13427                                 CONTINUE;
13428                         END;
13429                         EXIT;
13430                 END LOOP;
13431         END LOOP;
13432
13433         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13434
13435     -- vandelay.*
13436         -- transfer queues the same way we transfer buckets (see above)
13437         FOR queue_row in
13438                 SELECT id, name
13439                 FROM   vandelay.queue
13440                 WHERE  owner = src_usr
13441         LOOP
13442                 suffix := ' (' || src_usr || ')';
13443                 LOOP
13444                         BEGIN
13445                                 UPDATE  vandelay.queue
13446                                 SET     owner = dest_usr, name = name || suffix
13447                                 WHERE   id = queue_row.id;
13448                         EXCEPTION WHEN unique_violation THEN
13449                                 suffix := suffix || ' ';
13450                                 CONTINUE;
13451                         END;
13452                         EXIT;
13453                 END LOOP;
13454         END LOOP;
13455
13456     -- money.*
13457     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13458     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13459     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13460     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13461     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13462
13463     -- action.*
13464     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13465     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13466     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13467
13468     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13469     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13470     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13471     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13472
13473     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13474     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13475     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13476     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13477     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13478
13479     -- acq.*
13480     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13481         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13482
13483         -- transfer picklists the same way we transfer buckets (see above)
13484         FOR picklist_row in
13485                 SELECT id, name
13486                 FROM   acq.picklist
13487                 WHERE  owner = src_usr
13488         LOOP
13489                 suffix := ' (' || src_usr || ')';
13490                 LOOP
13491                         BEGIN
13492                                 UPDATE  acq.picklist
13493                                 SET     owner = dest_usr, name = name || suffix
13494                                 WHERE   id = picklist_row.id;
13495                         EXCEPTION WHEN unique_violation THEN
13496                                 suffix := suffix || ' ';
13497                                 CONTINUE;
13498                         END;
13499                         EXIT;
13500                 END LOOP;
13501         END LOOP;
13502
13503     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13504     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13505     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13506     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13507     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13508     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13509     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13510     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13511
13512     -- asset.*
13513     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13514     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13515     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13516     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13517     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13518     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13519
13520     -- serial.*
13521     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13522     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13523
13524     -- reporter.*
13525     -- It's not uncommon to define the reporter schema in a replica 
13526     -- DB only, so don't assume these tables exist in the write DB.
13527     BEGIN
13528         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13529     EXCEPTION WHEN undefined_table THEN
13530         -- do nothing
13531     END;
13532     BEGIN
13533         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13534     EXCEPTION WHEN undefined_table THEN
13535         -- do nothing
13536     END;
13537     BEGIN
13538         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13539     EXCEPTION WHEN undefined_table THEN
13540         -- do nothing
13541     END;
13542     BEGIN
13543                 -- transfer folders the same way we transfer buckets (see above)
13544                 FOR folder_row in
13545                         SELECT id, name
13546                         FROM   reporter.template_folder
13547                         WHERE  owner = src_usr
13548                 LOOP
13549                         suffix := ' (' || src_usr || ')';
13550                         LOOP
13551                                 BEGIN
13552                                         UPDATE  reporter.template_folder
13553                                         SET     owner = dest_usr, name = name || suffix
13554                                         WHERE   id = folder_row.id;
13555                                 EXCEPTION WHEN unique_violation THEN
13556                                         suffix := suffix || ' ';
13557                                         CONTINUE;
13558                                 END;
13559                                 EXIT;
13560                         END LOOP;
13561                 END LOOP;
13562     EXCEPTION WHEN undefined_table THEN
13563         -- do nothing
13564     END;
13565     BEGIN
13566                 -- transfer folders the same way we transfer buckets (see above)
13567                 FOR folder_row in
13568                         SELECT id, name
13569                         FROM   reporter.report_folder
13570                         WHERE  owner = src_usr
13571                 LOOP
13572                         suffix := ' (' || src_usr || ')';
13573                         LOOP
13574                                 BEGIN
13575                                         UPDATE  reporter.report_folder
13576                                         SET     owner = dest_usr, name = name || suffix
13577                                         WHERE   id = folder_row.id;
13578                                 EXCEPTION WHEN unique_violation THEN
13579                                         suffix := suffix || ' ';
13580                                         CONTINUE;
13581                                 END;
13582                                 EXIT;
13583                         END LOOP;
13584                 END LOOP;
13585     EXCEPTION WHEN undefined_table THEN
13586         -- do nothing
13587     END;
13588     BEGIN
13589                 -- transfer folders the same way we transfer buckets (see above)
13590                 FOR folder_row in
13591                         SELECT id, name
13592                         FROM   reporter.output_folder
13593                         WHERE  owner = src_usr
13594                 LOOP
13595                         suffix := ' (' || src_usr || ')';
13596                         LOOP
13597                                 BEGIN
13598                                         UPDATE  reporter.output_folder
13599                                         SET     owner = dest_usr, name = name || suffix
13600                                         WHERE   id = folder_row.id;
13601                                 EXCEPTION WHEN unique_violation THEN
13602                                         suffix := suffix || ' ';
13603                                         CONTINUE;
13604                                 END;
13605                                 EXIT;
13606                         END LOOP;
13607                 END LOOP;
13608     EXCEPTION WHEN undefined_table THEN
13609         -- do nothing
13610     END;
13611
13612     -- Finally, delete the source user
13613     DELETE FROM actor.usr WHERE id = src_usr;
13614
13615 END;
13616 $$ LANGUAGE plpgsql;
13617
13618 -- The "add" trigger functions should protect against existing NULLed values, just in case
13619 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13620 BEGIN
13621     IF NOT NEW.voided THEN
13622         UPDATE  money.materialized_billable_xact_summary
13623           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13624             last_billing_ts = NEW.billing_ts,
13625             last_billing_note = NEW.note,
13626             last_billing_type = NEW.billing_type,
13627             balance_owed = balance_owed + NEW.amount
13628           WHERE id = NEW.xact;
13629     END IF;
13630
13631     RETURN NEW;
13632 END;
13633 $$ LANGUAGE PLPGSQL;
13634
13635 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13636 BEGIN
13637     IF NOT NEW.voided THEN
13638         UPDATE  money.materialized_billable_xact_summary
13639           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13640             last_payment_ts = NEW.payment_ts,
13641             last_payment_note = NEW.note,
13642             last_payment_type = TG_ARGV[0],
13643             balance_owed = balance_owed - NEW.amount
13644           WHERE id = NEW.xact;
13645     END IF;
13646
13647     RETURN NEW;
13648 END;
13649 $$ LANGUAGE PLPGSQL;
13650
13651 -- Refresh the mat view with the corrected underlying view
13652 TRUNCATE money.materialized_billable_xact_summary;
13653 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13654
13655 -- Now redefine the view as a window onto the materialized view
13656 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13657     SELECT * FROM money.materialized_billable_xact_summary;
13658
13659 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13660     user_id    IN INTEGER,
13661     perm_code  IN TEXT
13662 )
13663 RETURNS SETOF INTEGER AS $$
13664 --
13665 -- Return a set of all the org units for which a given user has a given
13666 -- permission, granted directly (not through inheritance from a parent
13667 -- org unit).
13668 --
13669 -- The permissions apply to a minimum depth of the org unit hierarchy,
13670 -- for the org unit(s) to which the user is assigned.  (They also apply
13671 -- to the subordinates of those org units, but we don't report the
13672 -- subordinates here.)
13673 --
13674 -- For purposes of this function, the permission.usr_work_ou_map table
13675 -- defines which users belong to which org units.  I.e. we ignore the
13676 -- home_ou column of actor.usr.
13677 --
13678 -- The result set may contain duplicates, which should be eliminated
13679 -- by a DISTINCT clause.
13680 --
13681 DECLARE
13682     b_super       BOOLEAN;
13683     n_perm        INTEGER;
13684     n_min_depth   INTEGER;
13685     n_work_ou     INTEGER;
13686     n_curr_ou     INTEGER;
13687     n_depth       INTEGER;
13688     n_curr_depth  INTEGER;
13689 BEGIN
13690     --
13691     -- Check for superuser
13692     --
13693     SELECT INTO b_super
13694         super_user
13695     FROM
13696         actor.usr
13697     WHERE
13698         id = user_id;
13699     --
13700     IF NOT FOUND THEN
13701         return;             -- No user?  No permissions.
13702     ELSIF b_super THEN
13703         --
13704         -- Super user has all permissions everywhere
13705         --
13706         FOR n_work_ou IN
13707             SELECT
13708                 id
13709             FROM
13710                 actor.org_unit
13711             WHERE
13712                 parent_ou IS NULL
13713         LOOP
13714             RETURN NEXT n_work_ou;
13715         END LOOP;
13716         RETURN;
13717     END IF;
13718     --
13719     -- Translate the permission name
13720     -- to a numeric permission id
13721     --
13722     SELECT INTO n_perm
13723         id
13724     FROM
13725         permission.perm_list
13726     WHERE
13727         code = perm_code;
13728     --
13729     IF NOT FOUND THEN
13730         RETURN;               -- No such permission
13731     END IF;
13732     --
13733     -- Find the highest-level org unit (i.e. the minimum depth)
13734     -- to which the permission is applied for this user
13735     --
13736     -- This query is modified from the one in permission.usr_perms().
13737     --
13738     SELECT INTO n_min_depth
13739         min( depth )
13740     FROM    (
13741         SELECT depth
13742           FROM permission.usr_perm_map upm
13743          WHERE upm.usr = user_id
13744            AND (upm.perm = n_perm OR upm.perm = -1)
13745                     UNION
13746         SELECT  gpm.depth
13747           FROM  permission.grp_perm_map gpm
13748           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13749             AND gpm.grp IN (
13750                SELECT   (permission.grp_ancestors(
13751                     (SELECT profile FROM actor.usr WHERE id = user_id)
13752                 )).id
13753             )
13754                     UNION
13755         SELECT  p.depth
13756           FROM  permission.grp_perm_map p
13757           WHERE (p.perm = n_perm OR p.perm = -1)
13758             AND p.grp IN (
13759                 SELECT (permission.grp_ancestors(m.grp)).id
13760                 FROM   permission.usr_grp_map m
13761                 WHERE  m.usr = user_id
13762             )
13763     ) AS x;
13764     --
13765     IF NOT FOUND THEN
13766         RETURN;                -- No such permission for this user
13767     END IF;
13768     --
13769     -- Identify the org units to which the user is assigned.  Note that
13770     -- we pay no attention to the home_ou column in actor.usr.
13771     --
13772     FOR n_work_ou IN
13773         SELECT
13774             work_ou
13775         FROM
13776             permission.usr_work_ou_map
13777         WHERE
13778             usr = user_id
13779     LOOP            -- For each org unit to which the user is assigned
13780         --
13781         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13782         -- We take it on faith that this depth agrees with the actual hierarchy
13783         -- defined in actor.org_unit.
13784         --
13785         SELECT INTO n_depth
13786             type.depth
13787         FROM
13788             actor.org_unit_type type
13789                 INNER JOIN actor.org_unit ou
13790                     ON ( ou.ou_type = type.id )
13791         WHERE
13792             ou.id = n_work_ou;
13793         --
13794         IF NOT FOUND THEN
13795             CONTINUE;        -- Maybe raise exception?
13796         END IF;
13797         --
13798         -- Compare the depth of the work org unit to the
13799         -- minimum depth, and branch accordingly
13800         --
13801         IF n_depth = n_min_depth THEN
13802             --
13803             -- The org unit is at the right depth, so return it.
13804             --
13805             RETURN NEXT n_work_ou;
13806         ELSIF n_depth > n_min_depth THEN
13807             --
13808             -- Traverse the org unit tree toward the root,
13809             -- until you reach the minimum depth determined above
13810             --
13811             n_curr_depth := n_depth;
13812             n_curr_ou := n_work_ou;
13813             WHILE n_curr_depth > n_min_depth LOOP
13814                 SELECT INTO n_curr_ou
13815                     parent_ou
13816                 FROM
13817                     actor.org_unit
13818                 WHERE
13819                     id = n_curr_ou;
13820                 --
13821                 IF FOUND THEN
13822                     n_curr_depth := n_curr_depth - 1;
13823                 ELSE
13824                     --
13825                     -- This can happen only if the hierarchy defined in
13826                     -- actor.org_unit is corrupted, or out of sync with
13827                     -- the depths defined in actor.org_unit_type.
13828                     -- Maybe we should raise an exception here, instead
13829                     -- of silently ignoring the problem.
13830                     --
13831                     n_curr_ou = NULL;
13832                     EXIT;
13833                 END IF;
13834             END LOOP;
13835             --
13836             IF n_curr_ou IS NOT NULL THEN
13837                 RETURN NEXT n_curr_ou;
13838             END IF;
13839         ELSE
13840             --
13841             -- The permission applies only at a depth greater than the work org unit.
13842             -- Use connectby() to find all dependent org units at the specified depth.
13843             --
13844             FOR n_curr_ou IN
13845                 SELECT ou::INTEGER
13846                 FROM connectby(
13847                         'actor.org_unit',         -- table name
13848                         'id',                     -- key column
13849                         'parent_ou',              -- recursive foreign key
13850                         n_work_ou::TEXT,          -- id of starting point
13851                         (n_min_depth - n_depth)   -- max depth to search, relative
13852                     )                             --   to starting point
13853                     AS t(
13854                         ou text,            -- dependent org unit
13855                         parent_ou text,     -- (ignore)
13856                         level int           -- depth relative to starting point
13857                     )
13858                 WHERE
13859                     level = n_min_depth - n_depth
13860             LOOP
13861                 RETURN NEXT n_curr_ou;
13862             END LOOP;
13863         END IF;
13864         --
13865     END LOOP;
13866     --
13867     RETURN;
13868     --
13869 END;
13870 $$ LANGUAGE 'plpgsql';
13871
13872 ALTER TABLE acq.purchase_order
13873         ADD COLUMN cancel_reason INT
13874                 REFERENCES acq.cancel_reason( id )
13875             DEFERRABLE INITIALLY DEFERRED,
13876         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
13877
13878 -- Build the history table and lifecycle view
13879 -- for acq.purchase_order
13880
13881 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
13882
13883 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
13884
13885 ALTER TABLE acq.lineitem
13886         ADD COLUMN cancel_reason INT
13887                 REFERENCES acq.cancel_reason( id )
13888             DEFERRABLE INITIALLY DEFERRED,
13889         ADD COLUMN estimated_unit_price NUMERIC,
13890         ADD COLUMN claim_policy INT
13891                 REFERENCES acq.claim_policy
13892                 DEFERRABLE INITIALLY DEFERRED,
13893         ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
13894
13895 -- Build the history table and lifecycle view
13896 -- for acq.lineitem
13897
13898 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
13899 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
13900
13901 ALTER TABLE acq.lineitem_detail
13902         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
13903                                             DEFERRABLE INITIALLY DEFERRED;
13904
13905 ALTER TABLE acq.lineitem_detail
13906         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
13907
13908 ALTER TABLE acq.lineitem_detail
13909         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13910                 ON DELETE CASCADE
13911                 DEFERRABLE INITIALLY DEFERRED;
13912
13913 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
13914
13915 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
13916         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
13917
13918 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
13919         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
13920
13921 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
13922
13923     use MARC::Record;
13924     use MARC::File::XML (BinaryEncoding => 'UTF-8');
13925     use strict;
13926
13927     my $target_xml = shift;
13928     my $source_xml = shift;
13929     my $field_spec = shift;
13930
13931     my $target_r = MARC::Record->new_from_xml( $target_xml );
13932     my $source_r = MARC::Record->new_from_xml( $source_xml );
13933
13934     return $target_xml unless ($target_r && $source_r);
13935
13936     my @field_list = split(',', $field_spec);
13937
13938     my %fields;
13939     for my $f (@field_list) {
13940         $f =~ s/^\s*//; $f =~ s/\s*$//;
13941         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
13942             my $field = $1;
13943             $field =~ s/\s+//;
13944             my $sf = $2;
13945             $sf =~ s/\s+//;
13946             my $match = $3;
13947             $match =~ s/^\s*//; $match =~ s/\s*$//;
13948             $fields{$field} = { sf => [ split('', $sf) ] };
13949             if ($match) {
13950                 my ($msf,$mre) = split('~', $match);
13951                 if (length($msf) > 0 and length($mre) > 0) {
13952                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
13953                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
13954                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
13955                 }
13956             }
13957         }
13958     }
13959
13960     for my $f ( keys %fields) {
13961         if ( @{$fields{$f}{sf}} ) {
13962             for my $from_field ($source_r->field( $f )) {
13963                 for my $to_field ($target_r->field( $f )) {
13964                     if (exists($fields{$f}{match})) {
13965                         next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
13966                     }
13967                     my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
13968                     $to_field->add_subfields( @new_sf );
13969                 }
13970             }
13971         } else {
13972             my @new_fields = map { $_->clone } $source_r->field( $f );
13973             $target_r->insert_fields_ordered( @new_fields );
13974         }
13975     }
13976
13977     $target_xml = $target_r->as_xml_record;
13978     $target_xml =~ s/^<\?.+?\?>$//mo;
13979     $target_xml =~ s/\n//sgo;
13980     $target_xml =~ s/>\s+</></sgo;
13981
13982     return $target_xml;
13983
13984 $_$ LANGUAGE PLPERLU;
13985
13986 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
13987
13988     use MARC::Record;
13989     use MARC::File::XML (BinaryEncoding => 'UTF-8');
13990     use strict;
13991
13992     my $xml = shift;
13993     my $r = MARC::Record->new_from_xml( $xml );
13994
13995     return $xml unless ($r);
13996
13997     my $field_spec = shift;
13998     my @field_list = split(',', $field_spec);
13999
14000     my %fields;
14001     for my $f (@field_list) {
14002         $f =~ s/^\s*//; $f =~ s/\s*$//;
14003         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14004             my $field = $1;
14005             $field =~ s/\s+//;
14006             my $sf = $2;
14007             $sf =~ s/\s+//;
14008             my $match = $3;
14009             $match =~ s/^\s*//; $match =~ s/\s*$//;
14010             $fields{$field} = { sf => [ split('', $sf) ] };
14011             if ($match) {
14012                 my ($msf,$mre) = split('~', $match);
14013                 if (length($msf) > 0 and length($mre) > 0) {
14014                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14015                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14016                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14017                 }
14018             }
14019         }
14020     }
14021
14022     for my $f ( keys %fields) {
14023         for my $to_field ($r->field( $f )) {
14024             if (exists($fields{$f}{match})) {
14025                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14026             }
14027
14028             if ( @{$fields{$f}{sf}} ) {
14029                 $to_field->delete_subfield(code => $fields{$f}{sf});
14030             } else {
14031                 $r->delete_field( $to_field );
14032             }
14033         }
14034     }
14035
14036     $xml = $r->as_xml_record;
14037     $xml =~ s/^<\?.+?\?>$//mo;
14038     $xml =~ s/\n//sgo;
14039     $xml =~ s/>\s+</></sgo;
14040
14041     return $xml;
14042
14043 $_$ LANGUAGE PLPERLU;
14044
14045 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14046     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
14047 $_$ LANGUAGE SQL;
14048
14049 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14050     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
14051 $_$ LANGUAGE SQL;
14052
14053 CREATE VIEW action.unfulfilled_hold_max_loop AS
14054         SELECT  hold,
14055                 max(count) AS max
14056         FROM    action.unfulfilled_hold_loops
14057         GROUP BY 1;
14058
14059 ALTER TABLE acq.lineitem_attr
14060         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
14061
14062 ALTER TABLE acq.lineitem_attr
14063         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14064                 ON DELETE CASCADE
14065                 DEFERRABLE INITIALLY DEFERRED;
14066
14067 ALTER TABLE acq.po_note
14068         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
14069
14070 CREATE TABLE vandelay.merge_profile (
14071     id              BIGSERIAL   PRIMARY KEY,
14072     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
14073     name            TEXT        NOT NULL,
14074     add_spec        TEXT,
14075     replace_spec    TEXT,
14076     strip_spec      TEXT,
14077     preserve_spec   TEXT,
14078     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14079     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))
14080 );
14081
14082 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14083 DECLARE
14084     attr        RECORD;
14085     attr_def    RECORD;
14086     eg_rec      RECORD;
14087     id_value    TEXT;
14088     exact_id    BIGINT;
14089 BEGIN
14090
14091     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14092
14093     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14094
14095     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14096         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14097
14098         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14099             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14100             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14101             IF exact_id IS NOT NULL THEN
14102                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14103             END IF;
14104         END IF;
14105     END IF;
14106
14107     IF exact_id IS NULL THEN
14108         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
14109
14110             -- All numbers? check for an id match
14111             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14112                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14113                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14114                 END LOOP;
14115             END IF;
14116
14117             -- Looks like an ISBN? check for an isbn match
14118             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14119                 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
14120                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14121                     IF FOUND THEN
14122                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14123                     END IF;
14124                 END LOOP;
14125
14126                 -- subcheck for isbn-as-tcn
14127                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14128                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14129                 END LOOP;
14130             END IF;
14131
14132             -- check for an OCLC tcn_value match
14133             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14134                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14135                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14136                 END LOOP;
14137             END IF;
14138
14139             -- check for a direct tcn_value match
14140             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14141                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14142             END LOOP;
14143
14144             -- check for a direct item barcode match
14145             FOR eg_rec IN
14146                     SELECT  DISTINCT b.*
14147                       FROM  biblio.record_entry b
14148                             JOIN asset.call_number cn ON (cn.record = b.id)
14149                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14150                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14151             LOOP
14152                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14153             END LOOP;
14154
14155         END LOOP;
14156     END IF;
14157
14158     RETURN NULL;
14159 END;
14160 $func$ LANGUAGE PLPGSQL;
14161
14162 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 $_$
14163     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14164 $_$ LANGUAGE SQL;
14165
14166 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14167 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14168 DECLARE
14169     output              vandelay.compile_profile%ROWTYPE;
14170     profile             vandelay.merge_profile%ROWTYPE;
14171     profile_tmpl        TEXT;
14172     profile_tmpl_owner  TEXT;
14173     add_rule            TEXT := '';
14174     strip_rule          TEXT := '';
14175     replace_rule        TEXT := '';
14176     preserve_rule       TEXT := '';
14177
14178 BEGIN
14179
14180     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14181     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14182
14183     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14184         SELECT  p.* INTO profile
14185           FROM  vandelay.merge_profile p
14186                 JOIN actor.org_unit u ON (u.id = p.owner)
14187           WHERE p.name = profile_tmpl
14188                 AND u.shortname = profile_tmpl_owner;
14189
14190         IF profile.id IS NOT NULL THEN
14191             add_rule := COALESCE(profile.add_spec,'');
14192             strip_rule := COALESCE(profile.strip_spec,'');
14193             replace_rule := COALESCE(profile.replace_spec,'');
14194             preserve_rule := COALESCE(profile.preserve_spec,'');
14195         END IF;
14196     END IF;
14197
14198     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
14199     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
14200     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
14201     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
14202
14203     output.add_rule := BTRIM(add_rule,',');
14204     output.replace_rule := BTRIM(replace_rule,',');
14205     output.strip_rule := BTRIM(strip_rule,',');
14206     output.preserve_rule := BTRIM(preserve_rule,',');
14207
14208     RETURN output;
14209 END;
14210 $_$ LANGUAGE PLPGSQL;
14211
14212 -- Template-based marc munging functions
14213 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14214 DECLARE
14215     merge_profile   vandelay.merge_profile%ROWTYPE;
14216     dyn_profile     vandelay.compile_profile%ROWTYPE;
14217     editor_string   TEXT;
14218     editor_id       INT;
14219     source_marc     TEXT;
14220     target_marc     TEXT;
14221     eg_marc         TEXT;
14222     replace_rule    TEXT;
14223     match_count     INT;
14224 BEGIN
14225
14226     SELECT  b.marc INTO eg_marc
14227       FROM  biblio.record_entry b
14228       WHERE b.id = eg_id
14229       LIMIT 1;
14230
14231     IF eg_marc IS NULL OR v_marc IS NULL THEN
14232         -- RAISE NOTICE 'no marc for template or bib record';
14233         RETURN FALSE;
14234     END IF;
14235
14236     dyn_profile := vandelay.compile_profile( v_marc );
14237
14238     IF merge_profile_id IS NOT NULL THEN
14239         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14240         IF FOUND THEN
14241             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14242             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14243             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14244             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14245         END IF;
14246     END IF;
14247
14248     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14249         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14250         RETURN FALSE;
14251     END IF;
14252
14253     IF dyn_profile.replace_rule <> '' THEN
14254         source_marc = v_marc;
14255         target_marc = eg_marc;
14256         replace_rule = dyn_profile.replace_rule;
14257     ELSE
14258         source_marc = eg_marc;
14259         target_marc = v_marc;
14260         replace_rule = dyn_profile.preserve_rule;
14261     END IF;
14262
14263     UPDATE  biblio.record_entry
14264       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14265       WHERE id = eg_id;
14266
14267     IF NOT FOUND THEN
14268         -- RAISE NOTICE 'update of biblio.record_entry failed';
14269         RETURN FALSE;
14270     END IF;
14271
14272     RETURN TRUE;
14273
14274 END;
14275 $$ LANGUAGE PLPGSQL;
14276
14277 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14278     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14279 $$ LANGUAGE SQL;
14280
14281 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14282 DECLARE
14283     merge_profile   vandelay.merge_profile%ROWTYPE;
14284     dyn_profile     vandelay.compile_profile%ROWTYPE;
14285     editor_string   TEXT;
14286     editor_id       INT;
14287     source_marc     TEXT;
14288     target_marc     TEXT;
14289     eg_marc         TEXT;
14290     v_marc          TEXT;
14291     replace_rule    TEXT;
14292     match_count     INT;
14293 BEGIN
14294
14295     SELECT  q.marc INTO v_marc
14296       FROM  vandelay.queued_record q
14297             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14298       LIMIT 1;
14299
14300     IF v_marc IS NULL THEN
14301         -- RAISE NOTICE 'no marc for vandelay or bib record';
14302         RETURN FALSE;
14303     END IF;
14304
14305     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14306         UPDATE  vandelay.queued_bib_record
14307           SET   imported_as = eg_id,
14308                 import_time = NOW()
14309           WHERE id = import_id;
14310
14311         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14312
14313         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14314             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14315
14316             IF editor_id IS NULL THEN
14317                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14318             END IF;
14319
14320             IF editor_id IS NOT NULL THEN
14321                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14322             END IF;
14323         END IF;
14324
14325         RETURN TRUE;
14326     END IF;
14327
14328     -- RAISE NOTICE 'update of biblio.record_entry failed';
14329
14330     RETURN FALSE;
14331
14332 END;
14333 $$ LANGUAGE PLPGSQL;
14334
14335 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14336 DECLARE
14337     eg_id           BIGINT;
14338     match_count     INT;
14339     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14340 BEGIN
14341
14342     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14343
14344     IF FOUND THEN
14345         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14346         RETURN FALSE;
14347     END IF;
14348
14349     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14350
14351     IF match_count <> 1 THEN
14352         -- RAISE NOTICE 'not an exact match';
14353         RETURN FALSE;
14354     END IF;
14355
14356     SELECT  d.* INTO match_attr
14357       FROM  vandelay.bib_attr_definition d
14358             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14359             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14360       WHERE m.queued_record = import_id;
14361
14362     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14363         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14364         RETURN FALSE;
14365     END IF;
14366
14367     SELECT  m.eg_record INTO eg_id
14368       FROM  vandelay.bib_match m
14369       WHERE m.queued_record = import_id
14370       LIMIT 1;
14371
14372     IF eg_id IS NULL THEN
14373         RETURN FALSE;
14374     END IF;
14375
14376     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14377 END;
14378 $$ LANGUAGE PLPGSQL;
14379
14380 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14381 DECLARE
14382     queued_record   vandelay.queued_bib_record%ROWTYPE;
14383 BEGIN
14384
14385     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14386
14387         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14388             RETURN NEXT queued_record.id;
14389         END IF;
14390
14391     END LOOP;
14392
14393     RETURN;
14394
14395 END;
14396 $$ LANGUAGE PLPGSQL;
14397
14398 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14399     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14400 $$ LANGUAGE SQL;
14401
14402 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14403 DECLARE
14404     merge_profile   vandelay.merge_profile%ROWTYPE;
14405     dyn_profile     vandelay.compile_profile%ROWTYPE;
14406     source_marc     TEXT;
14407     target_marc     TEXT;
14408     eg_marc         TEXT;
14409     v_marc          TEXT;
14410     replace_rule    TEXT;
14411     match_count     INT;
14412 BEGIN
14413
14414     SELECT  b.marc INTO eg_marc
14415       FROM  authority.record_entry b
14416             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14417       LIMIT 1;
14418
14419     SELECT  q.marc INTO v_marc
14420       FROM  vandelay.queued_record q
14421             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14422       LIMIT 1;
14423
14424     IF eg_marc IS NULL OR v_marc IS NULL THEN
14425         -- RAISE NOTICE 'no marc for vandelay or authority record';
14426         RETURN FALSE;
14427     END IF;
14428
14429     dyn_profile := vandelay.compile_profile( v_marc );
14430
14431     IF merge_profile_id IS NOT NULL THEN
14432         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14433         IF FOUND THEN
14434             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14435             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14436             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14437             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14438         END IF;
14439     END IF;
14440
14441     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14442         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14443         RETURN FALSE;
14444     END IF;
14445
14446     IF dyn_profile.replace_rule <> '' THEN
14447         source_marc = v_marc;
14448         target_marc = eg_marc;
14449         replace_rule = dyn_profile.replace_rule;
14450     ELSE
14451         source_marc = eg_marc;
14452         target_marc = v_marc;
14453         replace_rule = dyn_profile.preserve_rule;
14454     END IF;
14455
14456     UPDATE  authority.record_entry
14457       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14458       WHERE id = eg_id;
14459
14460     IF FOUND THEN
14461         UPDATE  vandelay.queued_authority_record
14462           SET   imported_as = eg_id,
14463                 import_time = NOW()
14464           WHERE id = import_id;
14465         RETURN TRUE;
14466     END IF;
14467
14468     -- RAISE NOTICE 'update of authority.record_entry failed';
14469
14470     RETURN FALSE;
14471
14472 END;
14473 $$ LANGUAGE PLPGSQL;
14474
14475 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14476 DECLARE
14477     eg_id           BIGINT;
14478     match_count     INT;
14479 BEGIN
14480     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14481
14482     IF match_count <> 1 THEN
14483         -- RAISE NOTICE 'not an exact match';
14484         RETURN FALSE;
14485     END IF;
14486
14487     SELECT  m.eg_record INTO eg_id
14488       FROM  vandelay.authority_match m
14489       WHERE m.queued_record = import_id
14490       LIMIT 1;
14491
14492     IF eg_id IS NULL THEN
14493         RETURN FALSE;
14494     END IF;
14495
14496     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14497 END;
14498 $$ LANGUAGE PLPGSQL;
14499
14500 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14501 DECLARE
14502     queued_record   vandelay.queued_authority_record%ROWTYPE;
14503 BEGIN
14504
14505     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14506
14507         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14508             RETURN NEXT queued_record.id;
14509         END IF;
14510
14511     END LOOP;
14512
14513     RETURN;
14514
14515 END;
14516 $$ LANGUAGE PLPGSQL;
14517
14518 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14519     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14520 $$ LANGUAGE SQL;
14521
14522 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14523 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14524 DECLARE
14525     eg_tcn          TEXT;
14526     eg_tcn_source   TEXT;
14527     output          vandelay.tcn_data%ROWTYPE;
14528 BEGIN
14529
14530     -- 001/003
14531     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14532     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14533
14534         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14535         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14536             eg_tcn_source := 'System Local';
14537         END IF;
14538
14539         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14540
14541         IF NOT FOUND THEN
14542             output.used := FALSE;
14543         ELSE
14544             output.used := TRUE;
14545         END IF;
14546
14547         output.tcn := eg_tcn;
14548         output.tcn_source := eg_tcn_source;
14549         RETURN NEXT output;
14550
14551     END IF;
14552
14553     -- 901 ab
14554     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14555     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14556
14557         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14558         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14559             eg_tcn_source := 'System Local';
14560         END IF;
14561
14562         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14563
14564         IF NOT FOUND THEN
14565             output.used := FALSE;
14566         ELSE
14567             output.used := TRUE;
14568         END IF;
14569
14570         output.tcn := eg_tcn;
14571         output.tcn_source := eg_tcn_source;
14572         RETURN NEXT output;
14573
14574     END IF;
14575
14576     -- 039 ab
14577     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14578     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14579
14580         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14581         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14582             eg_tcn_source := 'System Local';
14583         END IF;
14584
14585         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14586
14587         IF NOT FOUND THEN
14588             output.used := FALSE;
14589         ELSE
14590             output.used := TRUE;
14591         END IF;
14592
14593         output.tcn := eg_tcn;
14594         output.tcn_source := eg_tcn_source;
14595         RETURN NEXT output;
14596
14597     END IF;
14598
14599     -- 020 a
14600     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14601     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14602
14603         eg_tcn_source := 'ISBN';
14604
14605         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14606
14607         IF NOT FOUND THEN
14608             output.used := FALSE;
14609         ELSE
14610             output.used := TRUE;
14611         END IF;
14612
14613         output.tcn := eg_tcn;
14614         output.tcn_source := eg_tcn_source;
14615         RETURN NEXT output;
14616
14617     END IF;
14618
14619     -- 022 a
14620     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14621     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14622
14623         eg_tcn_source := 'ISSN';
14624
14625         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14626
14627         IF NOT FOUND THEN
14628             output.used := FALSE;
14629         ELSE
14630             output.used := TRUE;
14631         END IF;
14632
14633         output.tcn := eg_tcn;
14634         output.tcn_source := eg_tcn_source;
14635         RETURN NEXT output;
14636
14637     END IF;
14638
14639     -- 010 a
14640     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14641     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14642
14643         eg_tcn_source := 'LCCN';
14644
14645         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14646
14647         IF NOT FOUND THEN
14648             output.used := FALSE;
14649         ELSE
14650             output.used := TRUE;
14651         END IF;
14652
14653         output.tcn := eg_tcn;
14654         output.tcn_source := eg_tcn_source;
14655         RETURN NEXT output;
14656
14657     END IF;
14658
14659     -- 035 a
14660     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
14661     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14662
14663         eg_tcn_source := 'System Legacy';
14664
14665         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14666
14667         IF NOT FOUND THEN
14668             output.used := FALSE;
14669         ELSE
14670             output.used := TRUE;
14671         END IF;
14672
14673         output.tcn := eg_tcn;
14674         output.tcn_source := eg_tcn_source;
14675         RETURN NEXT output;
14676
14677     END IF;
14678
14679     RETURN;
14680 END;
14681 $_$ LANGUAGE PLPGSQL;
14682
14683 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14684
14685 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);
14686
14687 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
14688
14689 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14690 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14691 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14692 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14693 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14694
14695 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14696 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14697 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14698 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14699
14700 ALTER TABLE metabib.series_field_entry
14701         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14702                 REFERENCES biblio.record_entry (id)
14703                 ON DELETE CASCADE
14704                 DEFERRABLE INITIALLY DEFERRED;
14705
14706 ALTER TABLE metabib.series_field_entry
14707         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14708                 REFERENCES config.metabib_field (id)
14709                 ON DELETE CASCADE
14710                 DEFERRABLE INITIALLY DEFERRED;
14711
14712 CREATE TABLE acq.claim_policy_action (
14713         id              SERIAL       PRIMARY KEY,
14714         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14715                                  ON DELETE CASCADE
14716                                      DEFERRABLE INITIALLY DEFERRED,
14717         action_interval INTERVAL     NOT NULL,
14718         action          INT          NOT NULL REFERENCES acq.claim_event_type
14719                                      DEFERRABLE INITIALLY DEFERRED,
14720         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14721 );
14722
14723 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14724 DECLARE
14725     value       TEXT;
14726     atype       TEXT;
14727     prov        INT;
14728     pos         INT;
14729     adef        RECORD;
14730     xpath_string    TEXT;
14731 BEGIN
14732     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14733  
14734         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14735  
14736         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14737             IF (atype = 'lineitem_provider_attr_definition') THEN
14738                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14739                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14740             END IF;
14741  
14742             IF (atype = 'lineitem_provider_attr_definition') THEN
14743                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14744             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14745                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14746             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14747                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14748             END IF;
14749  
14750             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14751  
14752             pos := 1;
14753  
14754             LOOP
14755                 SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14756  
14757                 IF (value IS NOT NULL AND value <> '') THEN
14758                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14759                         VALUES (NEW.id, adef.id, atype, adef.code, value);
14760                 ELSE
14761                     EXIT;
14762                 END IF;
14763  
14764                 pos := pos + 1;
14765             END LOOP;
14766  
14767         END IF;
14768  
14769     END LOOP;
14770  
14771     RETURN NULL;
14772 END;
14773 $function$ LANGUAGE PLPGSQL;
14774
14775 UPDATE config.metabib_field SET label = name;
14776 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14777
14778 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
14779          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
14780
14781 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
14782
14783 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
14784
14785 CREATE TABLE config.metabib_search_alias (
14786     alias       TEXT    PRIMARY KEY,
14787     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
14788     field       INT     REFERENCES config.metabib_field (id)
14789 );
14790
14791 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
14792 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
14793 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
14794 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
14795 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
14796 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
14797 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
14798 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
14799
14800 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
14801 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
14802 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
14803 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
14804 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
14805 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
14806 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
14807 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
14808 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
14809 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
14810 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
14811 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
14812 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
14813
14814 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
14815 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
14816 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
14817 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
14818 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
14819 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
14820 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
14821 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
14822
14823 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
14824 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
14825 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
14826 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
14827 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
14828 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
14829
14830 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
14831 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
14832 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
14833
14834 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
14835 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;
14836 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;
14837 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;
14838 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;
14839
14840 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
14841 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
14842 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
14843 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
14844
14845 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
14846 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
14847 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
14848 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
14849 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
14850 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
14851 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
14852 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
14853 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
14854 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
14855 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
14856 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
14857 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
14858 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
14859
14860 CREATE TABLE asset.opac_visible_copies (
14861   id        BIGINT primary key, -- copy id
14862   record    BIGINT,
14863   circ_lib  INTEGER
14864 );
14865 COMMENT ON TABLE asset.opac_visible_copies IS $$
14866 Materialized view of copies that are visible in the OPAC, used by
14867 search.query_parser_fts() to speed up OPAC visibility checks on large
14868 databases.  Contents are maintained by a set of triggers.
14869 $$;
14870 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
14871
14872 CREATE OR REPLACE FUNCTION search.query_parser_fts (
14873
14874     param_search_ou INT,
14875     param_depth     INT,
14876     param_query     TEXT,
14877     param_statuses  INT[],
14878     param_locations INT[],
14879     param_offset    INT,
14880     param_check     INT,
14881     param_limit     INT,
14882     metarecord      BOOL,
14883     staff           BOOL
14884  
14885 ) RETURNS SETOF search.search_result AS $func$
14886 DECLARE
14887
14888     current_res         search.search_result%ROWTYPE;
14889     search_org_list     INT[];
14890
14891     check_limit         INT;
14892     core_limit          INT;
14893     core_offset         INT;
14894     tmp_int             INT;
14895
14896     core_result         RECORD;
14897     core_cursor         REFCURSOR;
14898     core_rel_query      TEXT;
14899
14900     total_count         INT := 0;
14901     check_count         INT := 0;
14902     deleted_count       INT := 0;
14903     visible_count       INT := 0;
14904     excluded_count      INT := 0;
14905
14906 BEGIN
14907
14908     check_limit := COALESCE( param_check, 1000 );
14909     core_limit  := COALESCE( param_limit, 25000 );
14910     core_offset := COALESCE( param_offset, 0 );
14911
14912     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
14913
14914     IF param_search_ou > 0 THEN
14915         IF param_depth IS NOT NULL THEN
14916             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
14917         ELSE
14918             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
14919         END IF;
14920     ELSIF param_search_ou < 0 THEN
14921         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
14922     ELSIF param_search_ou = 0 THEN
14923         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
14924     END IF;
14925
14926     OPEN core_cursor FOR EXECUTE param_query;
14927
14928     LOOP
14929
14930         FETCH core_cursor INTO core_result;
14931         EXIT WHEN NOT FOUND;
14932         EXIT WHEN total_count >= core_limit;
14933
14934         total_count := total_count + 1;
14935
14936         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
14937
14938         check_count := check_count + 1;
14939
14940         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
14941         IF NOT FOUND THEN
14942             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
14943             deleted_count := deleted_count + 1;
14944             CONTINUE;
14945         END IF;
14946
14947         PERFORM 1
14948           FROM  biblio.record_entry b
14949                 JOIN config.bib_source s ON (b.source = s.id)
14950           WHERE s.transcendant
14951                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
14952
14953         IF FOUND THEN
14954             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
14955             visible_count := visible_count + 1;
14956
14957             current_res.id = core_result.id;
14958             current_res.rel = core_result.rel;
14959
14960             tmp_int := 1;
14961             IF metarecord THEN
14962                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
14963             END IF;
14964
14965             IF tmp_int = 1 THEN
14966                 current_res.record = core_result.records[1];
14967             ELSE
14968                 current_res.record = NULL;
14969             END IF;
14970
14971             RETURN NEXT current_res;
14972
14973             CONTINUE;
14974         END IF;
14975
14976         PERFORM 1
14977           FROM  asset.call_number cn
14978                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
14979                 JOIN asset.uri uri ON (map.uri = uri.id)
14980           WHERE NOT cn.deleted
14981                 AND cn.label = '##URI##'
14982                 AND uri.active
14983                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
14984                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
14985                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
14986           LIMIT 1;
14987
14988         IF FOUND THEN
14989             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
14990             visible_count := visible_count + 1;
14991
14992             current_res.id = core_result.id;
14993             current_res.rel = core_result.rel;
14994
14995             tmp_int := 1;
14996             IF metarecord THEN
14997                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
14998             END IF;
14999
15000             IF tmp_int = 1 THEN
15001                 current_res.record = core_result.records[1];
15002             ELSE
15003                 current_res.record = NULL;
15004             END IF;
15005
15006             RETURN NEXT current_res;
15007
15008             CONTINUE;
15009         END IF;
15010
15011         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
15012
15013             PERFORM 1
15014               FROM  asset.call_number cn
15015                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15016               WHERE NOT cn.deleted
15017                     AND NOT cp.deleted
15018                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
15019                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15020                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15021               LIMIT 1;
15022
15023             IF NOT FOUND THEN
15024                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
15025                 excluded_count := excluded_count + 1;
15026                 CONTINUE;
15027             END IF;
15028
15029         END IF;
15030
15031         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
15032
15033             PERFORM 1
15034               FROM  asset.call_number cn
15035                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15036               WHERE NOT cn.deleted
15037                     AND NOT cp.deleted
15038                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
15039                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15040                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15041               LIMIT 1;
15042
15043             IF NOT FOUND THEN
15044                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
15045                 excluded_count := excluded_count + 1;
15046                 CONTINUE;
15047             END IF;
15048
15049         END IF;
15050
15051         IF staff IS NULL OR NOT staff THEN
15052
15053             PERFORM 1
15054               FROM  asset.opac_visible_copies
15055               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15056                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15057               LIMIT 1;
15058
15059             IF NOT FOUND THEN
15060                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15061                 excluded_count := excluded_count + 1;
15062                 CONTINUE;
15063             END IF;
15064
15065         ELSE
15066
15067             PERFORM 1
15068               FROM  asset.call_number cn
15069                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15070                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15071               WHERE NOT cn.deleted
15072                     AND NOT cp.deleted
15073                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15074                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15075               LIMIT 1;
15076
15077             IF NOT FOUND THEN
15078
15079                 PERFORM 1
15080                   FROM  asset.call_number cn
15081                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15082                   LIMIT 1;
15083
15084                 IF FOUND THEN
15085                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15086                     excluded_count := excluded_count + 1;
15087                     CONTINUE;
15088                 END IF;
15089
15090             END IF;
15091
15092         END IF;
15093
15094         visible_count := visible_count + 1;
15095
15096         current_res.id = core_result.id;
15097         current_res.rel = core_result.rel;
15098
15099         tmp_int := 1;
15100         IF metarecord THEN
15101             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15102         END IF;
15103
15104         IF tmp_int = 1 THEN
15105             current_res.record = core_result.records[1];
15106         ELSE
15107             current_res.record = NULL;
15108         END IF;
15109
15110         RETURN NEXT current_res;
15111
15112         IF visible_count % 1000 = 0 THEN
15113             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15114         END IF;
15115
15116     END LOOP;
15117
15118     current_res.id = NULL;
15119     current_res.rel = NULL;
15120     current_res.record = NULL;
15121     current_res.total = total_count;
15122     current_res.checked = check_count;
15123     current_res.deleted = deleted_count;
15124     current_res.visible = visible_count;
15125     current_res.excluded = excluded_count;
15126
15127     CLOSE core_cursor;
15128
15129     RETURN NEXT current_res;
15130
15131 END;
15132 $func$ LANGUAGE PLPGSQL;
15133
15134 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15135 ALTER TABLE biblio.record_entry
15136          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15137          REFERENCES actor.org_unit (id)
15138          DEFERRABLE INITIALLY DEFERRED;
15139
15140 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15141
15142 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15143 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15144
15145 DROP VIEW auditor.biblio_record_entry_lifecycle;
15146
15147 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15148
15149 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15150         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15151 $$ LANGUAGE SQL STRICT IMMUTABLE;
15152
15153 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15154     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15155 $$ LANGUAGE SQL STRICT IMMUTABLE;
15156
15157 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15158     return lc(shift);
15159 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15160
15161 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15162     return uc(shift);
15163 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15164
15165 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15166     use Unicode::Normalize;
15167
15168     my $x = NFD(shift);
15169     $x =~ s/\pM+//go;
15170     return $x;
15171
15172 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15173
15174 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15175     use Unicode::Normalize;
15176
15177     my $x = NFC(shift);
15178     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15179     return $x;
15180
15181 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15182
15183 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15184 DECLARE
15185     setting RECORD;
15186     cur_org INT;
15187 BEGIN
15188     cur_org := org_id;
15189     LOOP
15190         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15191         IF FOUND THEN
15192             RETURN NEXT setting;
15193         END IF;
15194         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15195         EXIT WHEN cur_org IS NULL;
15196     END LOOP;
15197     RETURN;
15198 END;
15199 $$ LANGUAGE plpgsql STABLE;
15200
15201 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15202 DECLARE
15203     counter INT;
15204     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15205 BEGIN
15206
15207     SELECT  COUNT(*) INTO counter
15208       FROM  oils_xpath_table(
15209                 'id',
15210                 'marc',
15211                 'acq.lineitem',
15212                 '//*[@tag="' || tag || '"]',
15213                 'id=' || lineitem
15214             ) as t(i int,c text);
15215
15216     FOR i IN 1 .. counter LOOP
15217         FOR lida IN
15218             SELECT  *
15219               FROM  (   SELECT  id,i,t,v
15220                           FROM  oils_xpath_table(
15221                                     'id',
15222                                     'marc',
15223                                     'acq.lineitem',
15224                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15225                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15226                                     'id=' || lineitem
15227                                 ) as t(id int,t text,v text)
15228                     )x
15229         LOOP
15230             RETURN NEXT lida;
15231         END LOOP;
15232     END LOOP;
15233
15234     RETURN;
15235 END;
15236 $$ LANGUAGE PLPGSQL;
15237
15238 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15239 DECLARE
15240     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15241     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15242     result      config.i18n_core%ROWTYPE;
15243     fallback    TEXT;
15244     keyfield    TEXT := keyclass || '.' || keycol;
15245 BEGIN
15246
15247     -- Try the full locale
15248     SELECT  * INTO result
15249       FROM  config.i18n_core
15250       WHERE fq_field = keyfield
15251             AND identity_value = keyvalue
15252             AND translation = locale;
15253
15254     -- Try just the language
15255     IF NOT FOUND THEN
15256         SELECT  * INTO result
15257           FROM  config.i18n_core
15258           WHERE fq_field = keyfield
15259                 AND identity_value = keyvalue
15260                 AND translation = language;
15261     END IF;
15262
15263     -- Fall back to the string we passed in in the first place
15264     IF NOT FOUND THEN
15265     EXECUTE
15266             'SELECT ' ||
15267                 keycol ||
15268             ' FROM ' || keytable ||
15269             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15270                 INTO fallback;
15271         RETURN fallback;
15272     END IF;
15273
15274     RETURN result.string;
15275 END;
15276 $func$ LANGUAGE PLPGSQL STABLE;
15277
15278 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15279
15280 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15281
15282 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15283
15284 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15285     3, 1, 'delivered_but_lost',
15286     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15287
15288 CREATE TABLE config.global_flag (
15289     label   TEXT    NOT NULL
15290 ) INHERITS (config.internal_flag);
15291 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15292
15293 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
15294     VALUES (
15295         'cat.bib.use_id_for_tcn',
15296         oils_i18n_gettext(
15297             'cat.bib.use_id_for_tcn',
15298             'Cat: Use Internal ID for TCN Value',
15299             'cgf', 
15300             'label'
15301         )
15302     );
15303
15304 -- resolves performance issue noted by EG Indiana
15305
15306 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15307
15308 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15309
15310 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15311     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15312 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15313     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15314 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15315     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15316 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15317     (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 );
15318 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15319     (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 );
15320 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15321     (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 );
15322 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15323     (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 );
15324 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15325     (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 );
15326 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15327     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15328
15329 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15330  
15331
15332 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15333
15334 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15335 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15336 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15337 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15338 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15339 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15340
15341 CREATE TABLE metabib.identifier_field_entry (
15342         id              BIGSERIAL       PRIMARY KEY,
15343         source          BIGINT          NOT NULL,
15344         field           INT             NOT NULL,
15345         value           TEXT            NOT NULL,
15346         index_vector    tsvector        NOT NULL
15347 );
15348 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15349         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15350         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15351
15352 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15353 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15354     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15355 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15356
15357 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15358     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15359 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15360     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15361
15362 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15363     use Business::ISBN;
15364     use strict;
15365     use warnings;
15366
15367     # For each ISBN found in a single string containing a set of ISBNs:
15368     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15369     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15370
15371     my $input = shift;
15372     my $output = '';
15373
15374     foreach my $word (split(/\s/, $input)) {
15375         my $isbn = Business::ISBN->new($word);
15376
15377         # First check the checksum; if it is not valid, fix it and add the original
15378         # bad-checksum ISBN to the output
15379         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15380             $output .= $isbn->isbn() . " ";
15381             $isbn->fix_checksum();
15382         }
15383
15384         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15385         # and add the normalized original ISBN to the output
15386         if ($isbn && $isbn->is_valid()) {
15387             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15388             $output .= $isbn->isbn . " ";
15389
15390             # If we successfully converted the ISBN to its counterpart, add the
15391             # converted ISBN to the output as well
15392             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15393         }
15394     }
15395     return $output if $output;
15396
15397     # If there were no valid ISBNs, just return the raw input
15398     return $input;
15399 $func$ LANGUAGE PLPERLU;
15400
15401 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15402 /*
15403  * Copyright (C) 2010 Merrimack Valley Library Consortium
15404  * Jason Stephenson <jstephenson@mvlc.org>
15405  * Copyright (C) 2010 Laurentian University
15406  * Dan Scott <dscott@laurentian.ca>
15407  *
15408  * The translate_isbn1013 function takes an input ISBN and returns the
15409  * following in a single space-delimited string if the input ISBN is valid:
15410  *   - The normalized input ISBN (hyphens stripped)
15411  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15412  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15413  */
15414 $$;
15415
15416 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15417 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15418 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15419 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15420 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15421 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15422
15423 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15424         'ISBN 10/13 conversion',
15425         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15426         'translate_isbn1013',
15427         0
15428 );
15429
15430 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15431         'Replace',
15432         'Replace all occurences of first parameter in the string with the second parameter.',
15433         'replace',
15434         2
15435 );
15436
15437 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15438     SELECT  m.id, i.id, 1
15439       FROM  config.metabib_field m,
15440             config.index_normalizer i
15441       WHERE i.func IN ('first_word')
15442             AND m.id IN (18);
15443
15444 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15445     SELECT  m.id, i.id, 2
15446       FROM  config.metabib_field m,
15447             config.index_normalizer i
15448       WHERE i.func IN ('translate_isbn1013')
15449             AND m.id IN (18);
15450
15451 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15452     SELECT  m.id, i.id, $$['-','']$$
15453       FROM  config.metabib_field m,
15454             config.index_normalizer i
15455       WHERE i.func IN ('replace')
15456             AND m.id IN (19);
15457
15458 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15459     SELECT  m.id, i.id, $$[' ','']$$
15460       FROM  config.metabib_field m,
15461             config.index_normalizer i
15462       WHERE i.func IN ('replace')
15463             AND m.id IN (19);
15464
15465 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15466
15467 UPDATE  config.metabib_field_index_norm_map
15468   SET   params = REPLACE(params,E'\'','"')
15469   WHERE params IS NOT NULL AND params <> '';
15470
15471 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15472
15473 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15474
15475 ALTER TABLE config.circ_modifier
15476         ADD COLUMN avg_wait_time INTERVAL;
15477
15478 --CREATE TABLE actor.usr_password_reset (
15479 --  id SERIAL PRIMARY KEY,
15480 --  uuid TEXT NOT NULL, 
15481 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15482 --  request_time TIMESTAMP NOT NULL DEFAULT NOW(), 
15483 --  has_been_reset BOOL NOT NULL DEFAULT false
15484 --);
15485 --COMMENT ON TABLE actor.usr_password_reset IS $$
15486 --/*
15487 -- * Copyright (C) 2010 Laurentian University
15488 -- * Dan Scott <dscott@laurentian.ca>
15489 -- *
15490 -- * Self-serve password reset requests
15491 -- *
15492 -- * ****
15493 -- *
15494 -- * This program is free software; you can redistribute it and/or
15495 -- * modify it under the terms of the GNU General Public License
15496 -- * as published by the Free Software Foundation; either version 2
15497 -- * of the License, or (at your option) any later version.
15498 -- *
15499 -- * This program is distributed in the hope that it will be useful,
15500 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15501 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15502 -- * GNU General Public License for more details.
15503 -- */
15504 --$$;
15505 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15506 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15507 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15508 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15509
15510 -- Use the identifier search class tsconfig
15511 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15512 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15513     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15514     FOR EACH ROW
15515     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15516
15517 INSERT INTO config.global_flag (name,label,enabled)
15518     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15519 INSERT INTO config.global_flag (name,label,enabled)
15520     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15521
15522 -- turn a JSON scalar into an SQL TEXT value
15523 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15524     use JSON::XS;                    
15525     my $json = shift();
15526     my $txt;
15527     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15528     return undef if ($@);
15529     return $txt
15530 $f$ LANGUAGE PLPERLU;
15531
15532 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15533 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15534 DECLARE
15535     c               action.circulation%ROWTYPE;
15536     view_age        INTERVAL;
15537     usr_view_age    actor.usr_setting%ROWTYPE;
15538     usr_view_start  actor.usr_setting%ROWTYPE;
15539 BEGIN
15540     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15541     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15542
15543     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15544         -- User opted in and supplied a retention age
15545         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15546             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15547         ELSE
15548             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15549         END IF;
15550     ELSIF usr_view_start.value IS NOT NULL THEN
15551         -- User opted in
15552         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15553     ELSE
15554         -- User did not opt in
15555         RETURN;
15556     END IF;
15557
15558     FOR c IN
15559         SELECT  *
15560           FROM  action.circulation
15561           WHERE usr = usr_id
15562                 AND parent_circ IS NULL
15563                 AND xact_start > NOW() - view_age
15564           ORDER BY xact_start
15565     LOOP
15566         RETURN NEXT c;
15567     END LOOP;
15568
15569     RETURN;
15570 END;
15571 $func$ LANGUAGE PLPGSQL;
15572
15573 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15574 DECLARE
15575     usr_keep_age    actor.usr_setting%ROWTYPE;
15576     usr_keep_start  actor.usr_setting%ROWTYPE;
15577     org_keep_age    INTERVAL;
15578     org_keep_count  INT;
15579
15580     keep_age        INTERVAL;
15581
15582     target_acp      RECORD;
15583     circ_chain_head action.circulation%ROWTYPE;
15584     circ_chain_tail action.circulation%ROWTYPE;
15585
15586     purge_position  INT;
15587     count_purged    INT;
15588 BEGIN
15589
15590     count_purged := 0;
15591
15592     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15593
15594     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15595     IF org_keep_count IS NULL THEN
15596         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15597     END IF;
15598
15599     -- First, find copies with more than keep_count non-renewal circs
15600     FOR target_acp IN
15601         SELECT  target_copy,
15602                 COUNT(*) AS total_real_circs
15603           FROM  action.circulation
15604           WHERE parent_circ IS NULL
15605                 AND xact_finish IS NOT NULL
15606           GROUP BY target_copy
15607           HAVING COUNT(*) > org_keep_count
15608     LOOP
15609         purge_position := 0;
15610         -- And, for those, select circs that are finished and older than keep_age
15611         FOR circ_chain_head IN
15612             SELECT  *
15613               FROM  action.circulation
15614               WHERE target_copy = target_acp.target_copy
15615                     AND parent_circ IS NULL
15616               ORDER BY xact_start
15617         LOOP
15618
15619             -- Stop once we've purged enough circs to hit org_keep_count
15620             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15621
15622             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15623             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15624
15625             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15626             usr_keep_age.value := NULL;
15627             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15628
15629             usr_keep_start.value := NULL;
15630             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15631
15632             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15633                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15634                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15635                 ELSE
15636                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15637                 END IF;
15638             ELSIF usr_keep_start.value IS NOT NULL THEN
15639                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15640             ELSE
15641                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15642             END IF;
15643
15644             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15645
15646             -- We've passed the purging tests, purge the circ chain starting at the end
15647             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15648             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15649                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15650                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15651             END LOOP;
15652
15653             count_purged := count_purged + 1;
15654             purge_position := purge_position + 1;
15655
15656         END LOOP;
15657     END LOOP;
15658 END;
15659 $func$ LANGUAGE PLPGSQL;
15660
15661 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15662 DECLARE
15663     h               action.hold_request%ROWTYPE;
15664     view_age        INTERVAL;
15665     view_count      INT;
15666     usr_view_count  actor.usr_setting%ROWTYPE;
15667     usr_view_age    actor.usr_setting%ROWTYPE;
15668     usr_view_start  actor.usr_setting%ROWTYPE;
15669 BEGIN
15670     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15671     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15672     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15673
15674     FOR h IN
15675         SELECT  *
15676           FROM  action.hold_request
15677           WHERE usr = usr_id
15678                 AND fulfillment_time IS NULL
15679                 AND cancel_time IS NULL
15680           ORDER BY request_time DESC
15681     LOOP
15682         RETURN NEXT h;
15683     END LOOP;
15684
15685     IF usr_view_start.value IS NULL THEN
15686         RETURN;
15687     END IF;
15688
15689     IF usr_view_age.value IS NOT NULL THEN
15690         -- User opted in and supplied a retention age
15691         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15692             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15693         ELSE
15694             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15695         END IF;
15696     ELSE
15697         -- User opted in
15698         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15699     END IF;
15700
15701     IF usr_view_count.value IS NOT NULL THEN
15702         view_count := oils_json_to_text(usr_view_count.value)::INT;
15703     ELSE
15704         view_count := 1000;
15705     END IF;
15706
15707     -- show some fulfilled/canceled holds
15708     FOR h IN
15709         SELECT  *
15710           FROM  action.hold_request
15711           WHERE usr = usr_id
15712                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
15713                 AND request_time > NOW() - view_age
15714           ORDER BY request_time DESC
15715           LIMIT view_count
15716     LOOP
15717         RETURN NEXT h;
15718     END LOOP;
15719
15720     RETURN;
15721 END;
15722 $func$ LANGUAGE PLPGSQL;
15723
15724 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
15725
15726 DROP TABLE IF EXISTS serial.index_summary CASCADE;
15727
15728 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
15729
15730 DROP TABLE IF EXISTS serial.issuance CASCADE;
15731
15732 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
15733
15734 DROP TABLE IF EXISTS serial.subscription CASCADE;
15735
15736 CREATE TABLE asset.copy_template (
15737         id             SERIAL   PRIMARY KEY,
15738         owning_lib     INT      NOT NULL
15739                                 REFERENCES actor.org_unit (id)
15740                                 DEFERRABLE INITIALLY DEFERRED,
15741         creator        BIGINT   NOT NULL
15742                                 REFERENCES actor.usr (id)
15743                                 DEFERRABLE INITIALLY DEFERRED,
15744         editor         BIGINT   NOT NULL
15745                                 REFERENCES actor.usr (id)
15746                                 DEFERRABLE INITIALLY DEFERRED,
15747         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15748         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15749         name           TEXT     NOT NULL,
15750         -- columns above this point are attributes of the template itself
15751         -- columns after this point are attributes of the copy this template modifies/creates
15752         circ_lib       INT      REFERENCES actor.org_unit (id)
15753                                 DEFERRABLE INITIALLY DEFERRED,
15754         status         INT      REFERENCES config.copy_status (id)
15755                                 DEFERRABLE INITIALLY DEFERRED,
15756         location       INT      REFERENCES asset.copy_location (id)
15757                                 DEFERRABLE INITIALLY DEFERRED,
15758         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
15759                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
15760         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
15761                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
15762         age_protect    INT,
15763         circulate      BOOL,
15764         deposit        BOOL,
15765         ref            BOOL,
15766         holdable       BOOL,
15767         deposit_amount NUMERIC(6,2),
15768         price          NUMERIC(8,2),
15769         circ_modifier  TEXT,
15770         circ_as_type   TEXT,
15771         alert_message  TEXT,
15772         opac_visible   BOOL,
15773         floating       BOOL,
15774         mint_condition BOOL
15775 );
15776
15777 CREATE TABLE serial.subscription (
15778         id                     SERIAL       PRIMARY KEY,
15779         owning_lib             INT          NOT NULL DEFAULT 1
15780                                             REFERENCES actor.org_unit (id)
15781                                             ON DELETE SET NULL
15782                                             DEFERRABLE INITIALLY DEFERRED,
15783         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
15784         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
15785         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
15786                                             ON DELETE SET NULL
15787                                             DEFERRABLE INITIALLY DEFERRED,
15788         expected_date_offset   INTERVAL
15789         -- acquisitions/business-side tables link to here
15790 );
15791 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
15792 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
15793
15794 --at least one distribution per org_unit holding issues
15795 CREATE TABLE serial.distribution (
15796         id                    SERIAL  PRIMARY KEY,
15797         record_entry          BIGINT  REFERENCES serial.record_entry (id)
15798                                       ON DELETE SET NULL
15799                                       DEFERRABLE INITIALLY DEFERRED,
15800         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
15801                                           summary_method IS NULL
15802                                           OR summary_method IN ( 'add_to_sre',
15803                                           'merge_with_sre', 'use_sre_only',
15804                                           'use_sdist_only')),
15805         subscription          INT     NOT NULL
15806                                       REFERENCES serial.subscription (id)
15807                                                                   ON DELETE CASCADE
15808                                                                   DEFERRABLE INITIALLY DEFERRED,
15809         holding_lib           INT     NOT NULL
15810                                       REFERENCES actor.org_unit (id)
15811                                                                   DEFERRABLE INITIALLY DEFERRED,
15812         label                 TEXT    NOT NULL,
15813         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
15814                                       DEFERRABLE INITIALLY DEFERRED,
15815         receive_unit_template INT     REFERENCES asset.copy_template (id)
15816                                       DEFERRABLE INITIALLY DEFERRED,
15817         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
15818                                       DEFERRABLE INITIALLY DEFERRED,
15819         bind_unit_template    INT     REFERENCES asset.copy_template (id)
15820                                       DEFERRABLE INITIALLY DEFERRED,
15821         unit_label_prefix     TEXT,
15822         unit_label_suffix     TEXT
15823 );
15824 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
15825 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
15826
15827 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
15828
15829 CREATE TABLE serial.stream (
15830         id              SERIAL  PRIMARY KEY,
15831         distribution    INT     NOT NULL
15832                                 REFERENCES serial.distribution (id)
15833                                 ON DELETE CASCADE
15834                                 DEFERRABLE INITIALLY DEFERRED,
15835         routing_label   TEXT
15836 );
15837 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
15838
15839 CREATE UNIQUE INDEX label_once_per_dist
15840         ON serial.stream (distribution, routing_label)
15841         WHERE routing_label IS NOT NULL;
15842
15843 CREATE TABLE serial.routing_list_user (
15844         id             SERIAL       PRIMARY KEY,
15845         stream         INT          NOT NULL
15846                                     REFERENCES serial.stream
15847                                     ON DELETE CASCADE
15848                                     DEFERRABLE INITIALLY DEFERRED,
15849         pos            INT          NOT NULL DEFAULT 1,
15850         reader         INT          REFERENCES actor.usr
15851                                     ON DELETE CASCADE
15852                                     DEFERRABLE INITIALLY DEFERRED,
15853         department     TEXT,
15854         note           TEXT,
15855         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
15856         CONSTRAINT reader_or_dept CHECK
15857         (
15858             -- Recipient is a person or a department, but not both
15859                 (reader IS NOT NULL AND department IS NULL) OR
15860                 (reader IS NULL AND department IS NOT NULL)
15861         )
15862 );
15863 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
15864 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
15865
15866 CREATE TABLE serial.caption_and_pattern (
15867         id           SERIAL       PRIMARY KEY,
15868         subscription INT          NOT NULL REFERENCES serial.subscription (id)
15869                                   ON DELETE CASCADE
15870                                   DEFERRABLE INITIALLY DEFERRED,
15871         type         TEXT         NOT NULL
15872                                   CONSTRAINT cap_type CHECK ( type in
15873                                   ( 'basic', 'supplement', 'index' )),
15874         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
15875         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
15876         end_date     TIMESTAMP WITH TIME ZONE,
15877         active       BOOL         NOT NULL DEFAULT FALSE,
15878         pattern_code TEXT         NOT NULL,       -- must contain JSON
15879         enum_1       TEXT,
15880         enum_2       TEXT,
15881         enum_3       TEXT,
15882         enum_4       TEXT,
15883         enum_5       TEXT,
15884         enum_6       TEXT,
15885         chron_1      TEXT,
15886         chron_2      TEXT,
15887         chron_3      TEXT,
15888         chron_4      TEXT,
15889         chron_5      TEXT
15890 );
15891 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
15892
15893 CREATE TABLE serial.issuance (
15894         id              SERIAL    PRIMARY KEY,
15895         creator         INT       NOT NULL
15896                                   REFERENCES actor.usr (id)
15897                                                           DEFERRABLE INITIALLY DEFERRED,
15898         editor          INT       NOT NULL
15899                                   REFERENCES actor.usr (id)
15900                                   DEFERRABLE INITIALLY DEFERRED,
15901         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15902         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15903         subscription    INT       NOT NULL
15904                                   REFERENCES serial.subscription (id)
15905                                   ON DELETE CASCADE
15906                                   DEFERRABLE INITIALLY DEFERRED,
15907         label           TEXT,
15908         date_published  TIMESTAMP WITH TIME ZONE,
15909         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
15910                               DEFERRABLE INITIALLY DEFERRED,
15911         holding_code    TEXT,
15912         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
15913                                   (
15914                                       holding_type IS NULL
15915                                       OR holding_type IN ('basic','supplement','index')
15916                                   ),
15917         holding_link_id INT
15918         -- TODO: add columns for separate enumeration/chronology values
15919 );
15920 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
15921 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
15922 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
15923
15924 CREATE TABLE serial.unit (
15925         label           TEXT,
15926         label_sort_key  TEXT,
15927         contents        TEXT    NOT NULL
15928 ) INHERITS (asset.copy);
15929 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
15930 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
15931 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
15932 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
15933 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
15934
15935 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
15936
15937 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
15938
15939 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
15940
15941 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
15942
15943 CREATE TABLE serial.item (
15944         id              SERIAL  PRIMARY KEY,
15945         creator         INT     NOT NULL
15946                                 REFERENCES actor.usr (id)
15947                                 DEFERRABLE INITIALLY DEFERRED,
15948         editor          INT     NOT NULL
15949                                 REFERENCES actor.usr (id)
15950                                 DEFERRABLE INITIALLY DEFERRED,
15951         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15952         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15953         issuance        INT     NOT NULL
15954                                 REFERENCES serial.issuance (id)
15955                                 ON DELETE CASCADE
15956                                 DEFERRABLE INITIALLY DEFERRED,
15957         stream          INT     NOT NULL
15958                                 REFERENCES serial.stream (id)
15959                                 ON DELETE CASCADE
15960                                 DEFERRABLE INITIALLY DEFERRED,
15961         unit            INT     REFERENCES serial.unit (id)
15962                                 ON DELETE SET NULL
15963                                 DEFERRABLE INITIALLY DEFERRED,
15964         uri             INT     REFERENCES asset.uri (id)
15965                                 ON DELETE SET NULL
15966                                 DEFERRABLE INITIALLY DEFERRED,
15967         date_expected   TIMESTAMP WITH TIME ZONE,
15968         date_received   TIMESTAMP WITH TIME ZONE,
15969         status          TEXT    CONSTRAINT valid_status CHECK (
15970                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
15971                                'Expected', 'Not Held', 'Not Published', 'Received'))
15972                             DEFAULT 'Expected',
15973         shadowed        BOOL    NOT NULL DEFAULT FALSE
15974 );
15975 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
15976 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
15977 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
15978 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
15979 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
15980 CREATE INDEX serial_item_status_idx ON serial.item (status);
15981
15982 CREATE TABLE serial.item_note (
15983         id          SERIAL  PRIMARY KEY,
15984         item        INT     NOT NULL
15985                             REFERENCES serial.item (id)
15986                             ON DELETE CASCADE
15987                             DEFERRABLE INITIALLY DEFERRED,
15988         creator     INT     NOT NULL
15989                             REFERENCES actor.usr (id)
15990                             DEFERRABLE INITIALLY DEFERRED,
15991         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15992         pub         BOOL    NOT NULL    DEFAULT FALSE,
15993         title       TEXT    NOT NULL,
15994         value       TEXT    NOT NULL
15995 );
15996 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
15997
15998 CREATE TABLE serial.basic_summary (
15999         id                  SERIAL  PRIMARY KEY,
16000         distribution        INT     NOT NULL
16001                                     REFERENCES serial.distribution (id)
16002                                     ON DELETE CASCADE
16003                                     DEFERRABLE INITIALLY DEFERRED,
16004         generated_coverage  TEXT    NOT NULL,
16005         textual_holdings    TEXT,
16006         show_generated      BOOL    NOT NULL DEFAULT TRUE
16007 );
16008 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
16009
16010 CREATE TABLE serial.supplement_summary (
16011         id                  SERIAL  PRIMARY KEY,
16012         distribution        INT     NOT NULL
16013                                     REFERENCES serial.distribution (id)
16014                                     ON DELETE CASCADE
16015                                     DEFERRABLE INITIALLY DEFERRED,
16016         generated_coverage  TEXT    NOT NULL,
16017         textual_holdings    TEXT,
16018         show_generated      BOOL    NOT NULL DEFAULT TRUE
16019 );
16020 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
16021
16022 CREATE TABLE serial.index_summary (
16023         id                  SERIAL  PRIMARY KEY,
16024         distribution        INT     NOT NULL
16025                                     REFERENCES serial.distribution (id)
16026                                     ON DELETE CASCADE
16027                                     DEFERRABLE INITIALLY DEFERRED,
16028         generated_coverage  TEXT    NOT NULL,
16029         textual_holdings    TEXT,
16030         show_generated      BOOL    NOT NULL DEFAULT TRUE
16031 );
16032 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
16033
16034 -- 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.
16035
16036 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
16037 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16038
16039 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
16040 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;
16041
16042 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
16043 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16044
16045 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
16046 RETURNS INTEGER AS $$
16047 BEGIN
16048         RETURN EXTRACT( EPOCH FROM interval_val );
16049 END;
16050 $$ LANGUAGE plpgsql;
16051
16052 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
16053 RETURNS INTEGER AS $$
16054 BEGIN
16055         RETURN config.interval_to_seconds( interval_string::INTERVAL );
16056 END;
16057 $$ LANGUAGE plpgsql;
16058
16059 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
16060     'temp',
16061     oils_i18n_gettext(
16062         'temp',
16063         'Temporary bucket which gets deleted after use.',
16064         'cbrebt',
16065         'label'
16066     )
16067 );
16068
16069 -- 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.
16070
16071 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
16072 BEGIN
16073
16074     IF xml_is_well_formed(NEW.marc) THEN
16075         RETURN NEW;
16076     ELSE
16077         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16078     END IF;
16079     
16080 END;
16081 $func$ LANGUAGE PLPGSQL;
16082
16083 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();
16084
16085 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();
16086
16087 ALTER TABLE serial.record_entry
16088         ALTER COLUMN marc DROP NOT NULL;
16089
16090 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16091 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16092 <xsl:stylesheet
16093     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16094     xmlns:marc="http://www.loc.gov/MARC21/slim"
16095     version="1.0">
16096 <!--
16097 Copyright (C) 2010  Equinox Software, Inc.
16098 Galen Charlton <gmc@esilibrary.cOM.
16099
16100 This program is free software; you can redistribute it and/or
16101 modify it under the terms of the GNU General Public License
16102 as published by the Free Software Foundation; either version 2
16103 of the License, or (at your option) any later version.
16104
16105 This program is distributed in the hope that it will be useful,
16106 but WITHOUT ANY WARRANTY; without even the implied warranty of
16107 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16108 GNU General Public License for more details.
16109
16110 marc21_expand_880.xsl - stylesheet used during indexing to
16111                         map alternative graphical representations
16112                         of MARC fields stored in 880 fields
16113                         to the corresponding tag name and value.
16114
16115 For example, if a MARC record for a Chinese book has
16116
16117 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16118 880.00 $6 245-01/$1 $a八十三年短篇小說選
16119
16120 this stylesheet will transform it to the equivalent of
16121
16122 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16123 245.00 $6 245-01/$1 $a八十三年短篇小說選
16124
16125 -->
16126     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16127
16128     <xsl:template match="@*|node()">
16129         <xsl:copy>
16130             <xsl:apply-templates select="@*|node()"/>
16131         </xsl:copy>
16132     </xsl:template>
16133
16134     <xsl:template match="//marc:datafield[@tag='880']">
16135         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16136             <marc:datafield>
16137                 <xsl:attribute name="tag">
16138                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16139                 </xsl:attribute>
16140                 <xsl:attribute name="ind1">
16141                     <xsl:value-of select="@ind1" />
16142                 </xsl:attribute>
16143                 <xsl:attribute name="ind2">
16144                     <xsl:value-of select="@ind2" />
16145                 </xsl:attribute>
16146                 <xsl:apply-templates />
16147             </marc:datafield>
16148         </xsl:if>
16149     </xsl:template>
16150     
16151 </xsl:stylesheet>$$);
16152
16153 -- Splitting the ingest trigger up into little bits
16154
16155 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16156     flag INTEGER PRIMARY KEY
16157 ) ON COMMIT DROP;
16158 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16159
16160 -- cause failure if either of the tables we want to drop have rows
16161 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16162 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16163
16164 DROP TABLE IF EXISTS asset.copy_transparency_map;
16165 DROP TABLE IF EXISTS asset.copy_transparency;
16166
16167 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16168
16169 -- We won't necessarily use all of these, but they are here for completeness.
16170 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16171 -- Values are the EDI code value + 1000
16172
16173 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16174 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16175 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16176 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16177 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16178 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16179 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16180 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16181 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16182 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16183 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16184 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16185 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16186 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16187 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16188 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16189 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16190 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16191 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16192 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16193 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16194 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16195 ('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).'),
16196 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16197 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16198 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16199 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16200 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16201 ('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.'),
16202 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16203 ('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.'),
16204 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16205 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16206 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16207 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16208 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16209 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16210 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16211 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16212 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16213 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16214 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16215 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16216 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16217 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16218 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16219 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16220 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16221 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16222 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16223 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16224 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16225 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16226 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16227 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16228 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16229 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16230 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16231 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16232 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16233 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16234 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16235 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16236 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16237 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16238 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16239 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16240 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16241 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16242 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16243 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16244 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16245 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16246 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16247 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16248 ('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).'),
16249 ('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).'),
16250 ('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).'),
16251 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16252 ('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).'),
16253 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16254 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16255 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16256 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16257 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16258 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16259 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16260 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16261 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16262 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16263 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16264 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16265 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16266 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16267 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16268 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16269 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16270 ('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.'),
16271 ('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.'),
16272 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16273 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16274 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16275 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16276 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16277 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16278 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16279 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16280 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16281 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16282 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16283 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16284 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16285 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16286 ('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.'),
16287 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16288 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16289
16290 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16291     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16292  
16293 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16294  
16295 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16296         'Remove Parenthesized Substring',
16297         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16298         'remove_paren_substring',
16299         0
16300 );
16301
16302 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16303         'Trim Surrounding Space',
16304         'Trim leading and trailing spaces from extracted text.',
16305         'btrim',
16306         0
16307 );
16308
16309 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16310     SELECT  m.id,
16311             i.id,
16312             -2
16313       FROM  config.metabib_field m,
16314             config.index_normalizer i
16315       WHERE i.func IN ('remove_paren_substring')
16316             AND m.id IN (26);
16317
16318 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16319     SELECT  m.id,
16320             i.id,
16321             -1
16322       FROM  config.metabib_field m,
16323             config.index_normalizer i
16324       WHERE i.func IN ('btrim')
16325             AND m.id IN (26);
16326
16327 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16328 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16329 DECLARE
16330     dyn_profile     vandelay.compile_profile%ROWTYPE;
16331     replace_rule    TEXT;
16332     tmp_marc        TEXT;
16333     trgt_marc        TEXT;
16334     tmpl_marc        TEXT;
16335     match_count     INT;
16336 BEGIN
16337
16338     IF target_marc IS NULL OR template_marc IS NULL THEN
16339         -- RAISE NOTICE 'no marc for target or template record';
16340         RETURN NULL;
16341     END IF;
16342
16343     dyn_profile := vandelay.compile_profile( template_marc );
16344
16345     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16346         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16347         RETURN NULL;
16348     END IF;
16349
16350     IF dyn_profile.replace_rule <> '' THEN
16351         trgt_marc = target_marc;
16352         tmpl_marc = template_marc;
16353         replace_rule = dyn_profile.replace_rule;
16354     ELSE
16355         tmp_marc = target_marc;
16356         trgt_marc = template_marc;
16357         tmpl_marc = tmp_marc;
16358         replace_rule = dyn_profile.preserve_rule;
16359     END IF;
16360
16361     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16362
16363 END;
16364 $$ LANGUAGE PLPGSQL;
16365
16366 -- Function to generate an ephemeral overlay template from an authority record
16367 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16368
16369     use MARC::Record;
16370     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16371
16372     my $xml = shift;
16373     my $r = MARC::Record->new_from_xml( $xml );
16374
16375     return undef unless ($r);
16376
16377     my $id = shift() || $r->subfield( '901' => 'c' );
16378     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16379     return undef unless ($id); # We need an ID!
16380
16381     my $tmpl = MARC::Record->new();
16382
16383     my @rule_fields;
16384     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16385
16386         my $tag = $field->tag;
16387         my $i1 = $field->indicator(1);
16388         my $i2 = $field->indicator(2);
16389         my $sf = join '', map { $_->[0] } $field->subfields;
16390         my @data = map { @$_ } $field->subfields;
16391
16392         my @replace_them;
16393
16394         # Map the authority field to bib fields it can control.
16395         if ($tag >= 100 and $tag <= 111) {       # names
16396             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16397         } elsif ($tag eq '130') {                # uniform title
16398             @replace_them = qw/130 240 440 730 830/;
16399         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16400             @replace_them = ($tag + 500);
16401         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16402             @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/;
16403         } else {
16404             next;
16405         }
16406
16407         # Dummy up the bib-side data
16408         $tmpl->append_fields(
16409             map {
16410                 MARC::Field->new( $_, $i1, $i2, @data )
16411             } @replace_them
16412         );
16413
16414         # Construct some 'replace' rules
16415         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16416     }
16417
16418     # Insert the replace rules into the template
16419     $tmpl->append_fields(
16420         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16421     );
16422
16423     $xml = $tmpl->as_xml_record;
16424     $xml =~ s/^<\?.+?\?>$//mo;
16425     $xml =~ s/\n//sgo;
16426     $xml =~ s/>\s+</></sgo;
16427
16428     return $xml;
16429
16430 $func$ LANGUAGE PLPERLU;
16431
16432 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16433     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16434 $func$ LANGUAGE SQL;
16435
16436 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16437     SELECT authority.generate_overlay_template( $1, NULL );
16438 $func$ LANGUAGE SQL;
16439
16440 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16441 DELETE FROM config.metabib_field WHERE id = 26;
16442
16443 -- Making this a global_flag (UI accessible) instead of an internal_flag
16444 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16445     VALUES (
16446         'ingest.disable_authority_linking',
16447         oils_i18n_gettext(
16448             'ingest.disable_authority_linking',
16449             'Authority Automation: Disable bib-authority link tracking',
16450             'cgf', 
16451             'label'
16452         )
16453     );
16454 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16455 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16456
16457 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16458     VALUES (
16459         'ingest.disable_authority_auto_update',
16460         oils_i18n_gettext(
16461             'ingest.disable_authority_auto_update',
16462             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16463             'cgf', 
16464             'label'
16465         )
16466     );
16467
16468 -- Enable automated ingest of authority records; just insert the row into
16469 -- authority.record_entry and authority.full_rec will automatically be populated
16470
16471 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16472     UPDATE  biblio.record_entry
16473       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16474       WHERE id = $2;
16475     SELECT $1;
16476 $func$ LANGUAGE SQL;
16477
16478 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16479     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16480 $func$ LANGUAGE SQL;
16481
16482 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16483
16484 use MARC::Record;
16485 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16486
16487 my $xml = shift;
16488 my $r = MARC::Record->new_from_xml( $xml );
16489
16490 return_next( { tag => 'LDR', value => $r->leader } );
16491
16492 for my $f ( $r->fields ) {
16493     if ($f->is_control_field) {
16494         return_next({ tag => $f->tag, value => $f->data });
16495     } else {
16496         for my $s ($f->subfields) {
16497             return_next({
16498                 tag      => $f->tag,
16499                 ind1     => $f->indicator(1),
16500                 ind2     => $f->indicator(2),
16501                 subfield => $s->[0],
16502                 value    => $s->[1]
16503             });
16504
16505         }
16506     }
16507 }
16508
16509 return undef;
16510
16511 $func$ LANGUAGE PLPERLU;
16512
16513 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16514 DECLARE
16515     auth    authority.record_entry%ROWTYPE;
16516     output    authority.full_rec%ROWTYPE;
16517     field    RECORD;
16518 BEGIN
16519     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16520
16521     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16522         output.record := rid;
16523         output.ind1 := field.ind1;
16524         output.ind2 := field.ind2;
16525         output.tag := field.tag;
16526         output.subfield := field.subfield;
16527         IF field.subfield IS NOT NULL THEN
16528             output.value := naco_normalize(field.value, field.subfield);
16529         ELSE
16530             output.value := field.value;
16531         END IF;
16532
16533         CONTINUE WHEN output.value IS NULL;
16534
16535         RETURN NEXT output;
16536     END LOOP;
16537 END;
16538 $func$ LANGUAGE PLPGSQL;
16539
16540 -- authority.rec_descriptor appears to be unused currently
16541 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16542 BEGIN
16543     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16544 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16545 --        SELECT  auth_id, ;
16546
16547     RETURN;
16548 END;
16549 $func$ LANGUAGE PLPGSQL;
16550
16551 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16552 BEGIN
16553     DELETE FROM authority.full_rec WHERE record = auth_id;
16554     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16555         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16556
16557     RETURN;
16558 END;
16559 $func$ LANGUAGE PLPGSQL;
16560
16561 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16562 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16563 BEGIN
16564
16565     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16566         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16567         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
16568           -- Should remove matching $0 from controlled fields at the same time?
16569         RETURN NEW; -- and we're done
16570     END IF;
16571
16572     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16573         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16574
16575         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16576             RETURN NEW;
16577         END IF;
16578     END IF;
16579
16580     -- Flatten and insert the afr data
16581     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16582     IF NOT FOUND THEN
16583         PERFORM authority.reingest_authority_full_rec(NEW.id);
16584 -- authority.rec_descriptor is not currently used
16585 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16586 --        IF NOT FOUND THEN
16587 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16588 --        END IF;
16589     END IF;
16590
16591     RETURN NEW;
16592 END;
16593 $func$ LANGUAGE PLPGSQL;
16594
16595 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 ();
16596
16597 -- Some records manage to get XML namespace declarations into each element,
16598 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16599 -- This broke the old maintain_901(), so we'll make the regex more robust
16600
16601 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16602 BEGIN
16603     -- Remove any existing 901 fields before we insert the authoritative one
16604     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16605     IF TG_TABLE_SCHEMA = 'biblio' THEN
16606         NEW.marc := REGEXP_REPLACE(
16607             NEW.marc,
16608             E'(</(?:[^:]*?:)?record>)',
16609             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16610                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16611                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16612                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16613                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16614                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16615                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16616              E'</datafield>\\1'
16617         );
16618     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16619         NEW.marc := REGEXP_REPLACE(
16620             NEW.marc,
16621             E'(</(?:[^:]*?:)?record>)',
16622             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16623                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16624                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16625              E'</datafield>\\1'
16626         );
16627     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16628         NEW.marc := REGEXP_REPLACE(
16629             NEW.marc,
16630             E'(</(?:[^:]*?:)?record>)',
16631             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16632                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16633                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16634                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16635                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16636              E'</datafield>\\1'
16637         );
16638     ELSE
16639         NEW.marc := REGEXP_REPLACE(
16640             NEW.marc,
16641             E'(</(?:[^:]*?:)?record>)',
16642             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16643                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16644                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16645              E'</datafield>\\1'
16646         );
16647     END IF;
16648
16649     RETURN NEW;
16650 END;
16651 $func$ LANGUAGE PLPGSQL;
16652
16653 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16654 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16655 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16656  
16657 -- In booking, elbow room defines:
16658 --  a) how far in the future you must make a reservation on a given item if
16659 --      that item will have to transit somewhere to fulfill the reservation.
16660 --  b) how soon a reservation must be starting for the reserved item to
16661 --      be op-captured by the checkin interface.
16662
16663 -- We don't want to clobber any default_elbow room at any level:
16664
16665 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16666 DECLARE
16667     existing    actor.org_unit_setting%ROWTYPE;
16668 BEGIN
16669     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16670     IF NOT FOUND THEN
16671         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16672             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16673             'circ.booking_reservation.default_elbow_room',
16674             '"1 day"'
16675         );
16676         RETURN 1;
16677     END IF;
16678     RETURN 0;
16679 END;
16680 $$ LANGUAGE plpgsql;
16681
16682 SELECT pg_temp.default_elbow();
16683
16684 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16685
16686 -- returns the distinct set of target copy IDs from a user's visible circulation history
16687 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16688     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16689 $$ LANGUAGE SQL;
16690
16691 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
16692 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
16693 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
16694 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
16695
16696 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
16697
16698 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
16699 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
16700
16701 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16702     VALUES (
16703         'cat.maintain_control_numbers',
16704         oils_i18n_gettext(
16705             'cat.maintain_control_numbers',
16706             'Cat: Maintain 001/003/035 according to the MARC21 specification',
16707             'cgf', 
16708             'label'
16709         )
16710     );
16711
16712 INSERT INTO config.global_flag (name, label, enabled)
16713     VALUES (
16714         'circ.holds.empty_issuance_ok',
16715         oils_i18n_gettext(
16716             'circ.holds.empty_issuance_ok',
16717             'Holds: Allow holds on empty issuances',
16718             'cgf',
16719             'label'
16720         ),
16721         TRUE
16722     );
16723
16724 INSERT INTO config.global_flag (name, label, enabled)
16725     VALUES (
16726         'circ.holds.usr_not_requestor',
16727         oils_i18n_gettext(
16728             'circ.holds.usr_not_requestor',
16729             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
16730             'cgf',
16731             'label'
16732         ),
16733         TRUE
16734     );
16735
16736 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
16737 use strict;
16738 use MARC::Record;
16739 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16740 use Encode;
16741 use Unicode::Normalize;
16742
16743 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
16744 my $schema = $_TD->{table_schema};
16745 my $rec_id = $_TD->{new}{id};
16746
16747 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
16748 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
16749 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
16750     return;
16751 }
16752
16753 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
16754 my $ou_cni = 'EVRGRN';
16755
16756 my $owner;
16757 if ($schema eq 'serial') {
16758     $owner = $_TD->{new}{owning_lib};
16759 } else {
16760     # are.owner and bre.owner can be null, so fall back to the consortial setting
16761     $owner = $_TD->{new}{owner} || 1;
16762 }
16763
16764 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
16765 if ($ous_rv->{processed}) {
16766     $ou_cni = $ous_rv->{rows}[0]->{value};
16767     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
16768 } else {
16769     # Fall back to the shortname of the OU if there was no OU setting
16770     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
16771     if ($ous_rv->{processed}) {
16772         $ou_cni = $ous_rv->{rows}[0]->{shortname};
16773     }
16774 }
16775
16776 my ($create, $munge) = (0, 0);
16777 my ($orig_001, $orig_003) = ('', '');
16778
16779 # Incoming MARC records may have multiple 001s or 003s, despite the spec
16780 my @control_ids = $record->field('003');
16781 my @scns = $record->field('035');
16782
16783 foreach my $id_field ('001', '003') {
16784     my $spec_value;
16785     my @controls = $record->field($id_field);
16786
16787     if ($id_field eq '001') {
16788         $spec_value = $rec_id;
16789     } else {
16790         $spec_value = $ou_cni;
16791     }
16792
16793     # Create the 001/003 if none exist
16794     if (scalar(@controls) == 0) {
16795         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
16796         $create = 1;
16797     } elsif (scalar(@controls) > 1) {
16798         # Do we already have the right 001/003 value in the existing set?
16799         unless (grep $_->data() eq $spec_value, @controls) {
16800             $munge = 1;
16801         }
16802
16803         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
16804         foreach my $control (@controls) {
16805             unless ($control->data() eq $spec_value) {
16806                 $record->delete_field($control);
16807             }
16808         }
16809     } else {
16810         # Only one field; check to see if we need to munge it
16811         unless (grep $_->data() eq $spec_value, @controls) {
16812             $munge = 1;
16813         }
16814     }
16815 }
16816
16817 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
16818 if ($munge) {
16819     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
16820
16821     # Do not create duplicate 035 fields
16822     unless (grep $_->subfield('a') eq $scn, @scns) {
16823         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
16824     }
16825 }
16826
16827 # Set the 001/003 and update the MARC
16828 if ($create or $munge) {
16829     $record->field('001')->data($rec_id);
16830     $record->field('003')->data($ou_cni);
16831
16832     my $xml = $record->as_xml_record();
16833     $xml =~ s/\n//sgo;
16834     $xml =~ s/^<\?xml.+\?\s*>//go;
16835     $xml =~ s/>\s+</></go;
16836     $xml =~ s/\p{Cc}//go;
16837
16838     # Embed a version of OpenILS::Application::AppUtils->entityize()
16839     # to avoid having to set PERL5LIB for PostgreSQL as well
16840
16841     # If we are going to convert non-ASCII characters to XML entities,
16842     # we had better be dealing with a UTF8 string to begin with
16843     $xml = decode_utf8($xml);
16844
16845     $xml = NFC($xml);
16846
16847     # Convert raw ampersands to entities
16848     $xml =~ s/&(?!\S+;)/&amp;/gso;
16849
16850     # Convert Unicode characters to entities
16851     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
16852
16853     $xml =~ s/[\x00-\x1f]//go;
16854     $_TD->{new}{marc} = $xml;
16855
16856     return "MODIFY";
16857 }
16858
16859 return;
16860 $func$ LANGUAGE PLPERLU;
16861
16862 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16863 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16864 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16865
16866 INSERT INTO metabib.facet_entry (source, field, value)
16867     SELECT source, field, value FROM (
16868         SELECT * FROM metabib.author_field_entry
16869             UNION ALL
16870         SELECT * FROM metabib.keyword_field_entry
16871             UNION ALL
16872         SELECT * FROM metabib.identifier_field_entry
16873             UNION ALL
16874         SELECT * FROM metabib.title_field_entry
16875             UNION ALL
16876         SELECT * FROM metabib.subject_field_entry
16877             UNION ALL
16878         SELECT * FROM metabib.series_field_entry
16879         )x
16880     WHERE x.index_vector = '';
16881         
16882 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
16883 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
16884 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
16885 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
16886 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
16887 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
16888
16889 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
16890 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
16891 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
16892
16893 -- copy OPAC visibility materialized view
16894 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
16895
16896     TRUNCATE TABLE asset.opac_visible_copies;
16897
16898     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16899     SELECT  cp.id, cp.circ_lib, cn.record
16900     FROM  asset.copy cp
16901         JOIN asset.call_number cn ON (cn.id = cp.call_number)
16902         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
16903         JOIN asset.copy_location cl ON (cp.location = cl.id)
16904         JOIN config.copy_status cs ON (cp.status = cs.id)
16905         JOIN biblio.record_entry b ON (cn.record = b.id)
16906     WHERE NOT cp.deleted
16907         AND NOT cn.deleted
16908         AND NOT b.deleted
16909         AND cs.opac_visible
16910         AND cl.opac_visible
16911         AND cp.opac_visible
16912         AND a.opac_visible;
16913
16914 $$ LANGUAGE SQL;
16915 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
16916 Rebuild the copy OPAC visibility cache.  Useful during migrations.
16917 $$;
16918
16919 -- and actually populate the table
16920 SELECT asset.refresh_opac_visible_copies_mat_view();
16921
16922 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
16923 DECLARE
16924     add_query       TEXT;
16925     remove_query    TEXT;
16926     do_add          BOOLEAN := false;
16927     do_remove       BOOLEAN := false;
16928 BEGIN
16929     add_query := $$
16930             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16931                 SELECT  cp.id, cp.circ_lib, cn.record
16932                   FROM  asset.copy cp
16933                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
16934                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
16935                         JOIN asset.copy_location cl ON (cp.location = cl.id)
16936                         JOIN config.copy_status cs ON (cp.status = cs.id)
16937                         JOIN biblio.record_entry b ON (cn.record = b.id)
16938                   WHERE NOT cp.deleted
16939                         AND NOT cn.deleted
16940                         AND NOT b.deleted
16941                         AND cs.opac_visible
16942                         AND cl.opac_visible
16943                         AND cp.opac_visible
16944                         AND a.opac_visible
16945     $$;
16946  
16947     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
16948
16949     IF TG_OP = 'INSERT' THEN
16950
16951         IF TG_TABLE_NAME IN ('copy', 'unit') 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     -- handle items first, since with circulation activity
16961     -- their statuses change frequently
16962     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
16963
16964         IF OLD.location    <> NEW.location OR
16965            OLD.call_number <> NEW.call_number OR
16966            OLD.status      <> NEW.status OR
16967            OLD.circ_lib    <> NEW.circ_lib THEN
16968             -- any of these could change visibility, but
16969             -- we'll save some queries and not try to calculate
16970             -- the change directly
16971             do_remove := true;
16972             do_add := true;
16973         ELSE
16974
16975             IF OLD.deleted <> NEW.deleted THEN
16976                 IF NEW.deleted THEN
16977                     do_remove := true;
16978                 ELSE
16979                     do_add := true;
16980                 END IF;
16981             END IF;
16982
16983             IF OLD.opac_visible <> NEW.opac_visible THEN
16984                 IF OLD.opac_visible THEN
16985                     do_remove := true;
16986                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
16987                                         -- is also marked opac_visible
16988                     do_add := true;
16989                 END IF;
16990             END IF;
16991
16992         END IF;
16993
16994         IF do_remove THEN
16995             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
16996         END IF;
16997         IF do_add THEN
16998             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
16999             EXECUTE add_query;
17000         END IF;
17001
17002         RETURN NEW;
17003
17004     END IF;
17005
17006     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
17007  
17008         IF OLD.deleted AND NEW.deleted THEN -- do nothing
17009
17010             RETURN NEW;
17011  
17012         ELSIF NEW.deleted THEN -- remove rows
17013  
17014             IF TG_TABLE_NAME = 'call_number' THEN
17015                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
17016             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17017                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
17018             END IF;
17019  
17020             RETURN NEW;
17021  
17022         ELSIF OLD.deleted THEN -- add rows
17023  
17024             IF TG_TABLE_NAME IN ('copy','unit') THEN
17025                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17026             ELSIF TG_TABLE_NAME = 'call_number' THEN
17027                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17028             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17029                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
17030             END IF;
17031  
17032             EXECUTE add_query;
17033             RETURN NEW;
17034  
17035         END IF;
17036  
17037     END IF;
17038
17039     IF TG_TABLE_NAME = 'call_number' THEN
17040
17041         IF OLD.record <> NEW.record THEN
17042             -- call number is linked to different bib
17043             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
17044             EXECUTE remove_query;
17045             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17046             EXECUTE add_query;
17047         END IF;
17048
17049         RETURN NEW;
17050
17051     END IF;
17052
17053     IF TG_TABLE_NAME IN ('record_entry') THEN
17054         RETURN NEW; -- don't have 'opac_visible'
17055     END IF;
17056
17057     -- actor.org_unit, asset.copy_location, asset.copy_status
17058     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
17059
17060         RETURN NEW;
17061
17062     ELSIF NEW.opac_visible THEN -- add rows
17063
17064         IF TG_TABLE_NAME = 'org_unit' THEN
17065             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
17066         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17067             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
17068         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17069             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
17070         END IF;
17071  
17072         EXECUTE add_query;
17073  
17074     ELSE -- delete rows
17075
17076         IF TG_TABLE_NAME = 'org_unit' THEN
17077             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17078         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17079             remove_query := remove_query || 'location = ' || NEW.id || ');';
17080         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17081             remove_query := remove_query || 'status = ' || NEW.id || ');';
17082         END IF;
17083  
17084         EXECUTE remove_query;
17085  
17086     END IF;
17087  
17088     RETURN NEW;
17089 END;
17090 $func$ LANGUAGE PLPGSQL;
17091 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17092 Trigger function to update the copy OPAC visiblity cache.
17093 $$;
17094 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();
17095 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17096 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();
17097 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();
17098 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17099 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();
17100 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();
17101
17102 -- must create this rule explicitly; it is not inherited from asset.copy
17103 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;
17104
17105 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);
17106
17107 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17108 DECLARE
17109     moved_objects INT := 0;
17110     bib_id        INT := 0;
17111     bib_rec       biblio.record_entry%ROWTYPE;
17112     auth_link     authority.bib_linking%ROWTYPE;
17113 BEGIN
17114
17115     -- 1. Make source_record MARC a copy of the target_record to get auto-sync in linked bib records
17116     UPDATE authority.record_entry
17117       SET marc = (
17118         SELECT marc
17119           FROM authority.record_entry
17120           WHERE id = target_record
17121       )
17122       WHERE id = source_record;
17123
17124     -- 2. Update all bib records with the ID from target_record in their $0
17125     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre 
17126       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17127       WHERE abl.authority = target_record LOOP
17128
17129         UPDATE biblio.record_entry
17130           SET marc = REGEXP_REPLACE(marc, 
17131             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17132             E'\\1' || target_record || '<', 'g')
17133           WHERE id = bib_rec.id;
17134
17135           moved_objects := moved_objects + 1;
17136     END LOOP;
17137
17138     -- 3. "Delete" source_record
17139     DELETE FROM authority.record_entry
17140       WHERE id = source_record;
17141
17142     RETURN moved_objects;
17143 END;
17144 $func$ LANGUAGE plpgsql;
17145
17146 -- serial.record_entry already had an owner column spelled "owning_lib"
17147 -- Adjust the table and affected functions accordingly
17148
17149 ALTER TABLE serial.record_entry DROP COLUMN owner;
17150
17151 CREATE TABLE actor.usr_saved_search (
17152     id              SERIAL          PRIMARY KEY,
17153         owner           INT             NOT NULL REFERENCES actor.usr (id)
17154                                         ON DELETE CASCADE
17155                                         DEFERRABLE INITIALLY DEFERRED,
17156         name            TEXT            NOT NULL,
17157         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17158         query_text      TEXT            NOT NULL,
17159         query_type      TEXT            NOT NULL
17160                                         CONSTRAINT valid_query_text CHECK (
17161                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17162                                         -- we may add other types someday
17163         target          TEXT            NOT NULL
17164                                         CONSTRAINT valid_target CHECK (
17165                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17166         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17167 );
17168
17169 -- Apply Dan Wells' changes to the serial schema, from the
17170 -- seials-integration branch
17171
17172 CREATE TABLE serial.subscription_note (
17173         id           SERIAL PRIMARY KEY,
17174         subscription INT    NOT NULL
17175                             REFERENCES serial.subscription (id)
17176                             ON DELETE CASCADE
17177                             DEFERRABLE INITIALLY DEFERRED,
17178         creator      INT    NOT NULL
17179                             REFERENCES actor.usr (id)
17180                             DEFERRABLE INITIALLY DEFERRED,
17181         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17182         pub          BOOL   NOT NULL DEFAULT FALSE,
17183         title        TEXT   NOT NULL,
17184         value        TEXT   NOT NULL
17185 );
17186 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17187
17188 CREATE TABLE serial.distribution_note (
17189         id           SERIAL PRIMARY KEY,
17190         distribution INT    NOT NULL
17191                             REFERENCES serial.distribution (id)
17192                             ON DELETE CASCADE
17193                             DEFERRABLE INITIALLY DEFERRED,
17194         creator      INT    NOT NULL
17195                             REFERENCES actor.usr (id)
17196                             DEFERRABLE INITIALLY DEFERRED,
17197         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17198         pub          BOOL   NOT NULL DEFAULT FALSE,
17199         title        TEXT   NOT NULL,
17200         value        TEXT   NOT NULL
17201 );
17202 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17203
17204 ------- Begin surgery on serial.unit
17205
17206 ALTER TABLE serial.unit
17207         DROP COLUMN label;
17208
17209 ALTER TABLE serial.unit
17210         RENAME COLUMN label_sort_key TO sort_key;
17211
17212 ALTER TABLE serial.unit
17213         RENAME COLUMN contents TO detailed_contents;
17214
17215 ALTER TABLE serial.unit
17216         ADD COLUMN summary_contents TEXT;
17217
17218 UPDATE serial.unit
17219 SET summary_contents = detailed_contents;
17220
17221 ALTER TABLE serial.unit
17222         ALTER column summary_contents SET NOT NULL;
17223
17224 ------- End surgery on serial.unit
17225
17226 -- 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' );
17227
17228 -- Now rebuild the constraints dropped via cascade.
17229 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17230 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17231 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17232
17233 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17234
17235 DELETE FROM config.metabib_field_index_norm_map
17236     WHERE norm IN (
17237         SELECT id 
17238             FROM config.index_normalizer
17239             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17240     )
17241     AND field = 18
17242 ;
17243
17244 -- We won't necessarily use all of these, but they are here for completeness.
17245 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17246 -- Values are the EDI code value + 1200
17247
17248 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17249 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17250 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17251 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17252 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17253 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17254 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17255 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17256 (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.'),
17257 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17258 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17259 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17260 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17261 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17262 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17263 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17264 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17265 (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.'),
17266 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17267 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17268 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17269 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17270 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17271 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17272 (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.'),
17273 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17274 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17275 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17276 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17277 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17278 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17279 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17280 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17281 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17282 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17283 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17284 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17285 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17286 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17287 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17288 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17289 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17290 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17291 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17292 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17293 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17294 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17295 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17296 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17297 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17298 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17299 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17300 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17301 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17302 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17303 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17304 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17305 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17306 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17307 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17308 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17309 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17310 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17311 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17312 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17313 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17314 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17315 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17316 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17317 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17318 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17319 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17320 (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.'),
17321 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17322 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17323 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17324 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17325 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17326 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17327 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17328 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17329 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17330 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17331 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17332 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17333 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17334 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17335 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17336 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17337 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17338 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17339 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17340 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17341 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17342 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17343 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17344 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17345 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17346 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17347 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17348 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17349 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17350 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17351 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17352 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17353 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17354 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17355 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17356 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17357 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17358 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17359 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17360 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17361 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17362 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17363 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17364 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17365 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17366 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17367 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17368 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17369 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17370 (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.'),
17371 (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.'),
17372 (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.'),
17373 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17374 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17375 (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.'),
17376 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17377 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17378 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17379 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17380 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17381 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17382 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17383 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17384 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17385 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17386 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17387 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17388 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17389 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17390 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17391 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17392 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17393 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17394 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17395 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17396 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17397 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17398 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17399 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17400 (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.'),
17401 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17402 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17403 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17404 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17405 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17406 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17407 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17408 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17409 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17410 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17411 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17412 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17413 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17414 (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.'),
17415 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17416 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17417 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17418 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17419 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17420 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17421 (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.'),
17422 (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.'),
17423 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17424 (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.'),
17425 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17426 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17427 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17428 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17429 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17430 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17431 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17432 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17433 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17434 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17435 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17436 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17437 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17438 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17439 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17440 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17441 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17442 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17443 (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.'),
17444 (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.'),
17445 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17446 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17447 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17448 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17449 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17450 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17451 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17452 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17453 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17454 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17455 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17456 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17457 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17458 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17459 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17460 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17461 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17462 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17463 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17464 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17465 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17466 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17467 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17468 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17469 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17470 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17471 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17472 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17473 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17474 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17475 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17476 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17477 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17478 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17479 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17480 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17481 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17482 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17483 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17484 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17485 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17486 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17487 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17488 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17489 (1, 't', 1442, 'Number of months', 'The number of months.'),
17490 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17491 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17492 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17493 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17494 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17495 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17496 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17497 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17498 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17499 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17500 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17501 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17502 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17503 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17504 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17505 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17506 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17507 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17508 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17509 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17510 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17511 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17512 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17513 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17514 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17515 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17516 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17517 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17518 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17519 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17520 (1, 't', 1473, 'Agents', 'The number of agents.'),
17521 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17522 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17523 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17524 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17525 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17526 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17527 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17528 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17529 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17530 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17531 (1, 't', 1484, 'Departments', 'The number of departments.'),
17532 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17533 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17534 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17535 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17536 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17537 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17538 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17539 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17540 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17541 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17542 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17543 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17544 (1, 't', 1497, 'Executives', 'The number of executives.'),
17545 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17546 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17547 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17548 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17549 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17550 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17551 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17552 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17553 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17554 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17555 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17556 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17557 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17558 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17559 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17560 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17561 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17562 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17563 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17564 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17565 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17566 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17567 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17568 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17569 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17570 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17571 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17572 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17573 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17574 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17575 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17576 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17577 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17578 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17579 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17580 (1, 't', 1533, 'Seats',        'The number of seats.'),
17581 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17582 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17583 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17584 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17585 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17586 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17587 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17588 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17589 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17590 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17591 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17592 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17593 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17594 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17595 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17596 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17597 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17598 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17599 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17600 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17601 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17602 (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.'),
17603 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17604 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17605 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17606 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17607 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17608 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17609 (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.'),
17610 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17611 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17612 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17613 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17614 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17615 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17616 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17617 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17618 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17619 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17620 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17621 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17622 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17623 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17624 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17625 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17626 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17627 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17628 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17629 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17630 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17631 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17632 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17633 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17634 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17635 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17636 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17637 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17638 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17639 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17640 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17641 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17642 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17643 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17644 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17645 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17646 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17647 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17648 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17649 (1, 't', 1602, 'Patients',         'Number of patients.'),
17650 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17651 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17652 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17653 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17654 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17655 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17656 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17657 (1, 't', 1610, 'Operators',        'Number of operators.'),
17658 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17659 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17660 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17661 (1, 't', 1614, 'Machines',         'Number of machines.'),
17662 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17663 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17664 (1, 't', 1617, 'Directors',        'Number of directors.'),
17665 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17666 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17667 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17668 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17669 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17670 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17671 (1, 't', 1624, 'Beds', 'Number of beds.'),
17672 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17673 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17674 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17675 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
17676 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
17677 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
17678 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
17679 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
17680 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
17681 (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.'),
17682 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
17683 (1, 't', 1636, 'Professor', 'The number of professors.'),
17684 (1, 't', 1637, 'Seller',    'The number of sellers.'),
17685 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
17686 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
17687 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
17688 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
17689 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
17690 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
17691 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
17692 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
17693 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
17694 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
17695 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
17696 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
17697 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
17698 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
17699 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
17700 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
17701 (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.'),
17702 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
17703 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
17704 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
17705 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
17706 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
17707 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
17708 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
17709 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
17710 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
17711 (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.'),
17712 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
17713 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
17714 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
17715 (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.'),
17716 (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.'),
17717 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
17718 (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.'),
17719 (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.'),
17720 (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.'),
17721 (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.'),
17722 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
17723 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
17724 (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.'),
17725 (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.'),
17726 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
17727 (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.'),
17728 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
17729 (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.'),
17730 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
17731 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
17732 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
17733 (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).'),
17734 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
17735 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
17736 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
17737 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
17738 ;
17739 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
17740
17741 CREATE TABLE acq.serial_claim (
17742     id     SERIAL           PRIMARY KEY,
17743     type   INT              NOT NULL REFERENCES acq.claim_type
17744                                      DEFERRABLE INITIALLY DEFERRED,
17745     item    BIGINT          NOT NULL REFERENCES serial.item
17746                                      DEFERRABLE INITIALLY DEFERRED
17747 );
17748
17749 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
17750
17751 CREATE TABLE acq.serial_claim_event (
17752     id             BIGSERIAL        PRIMARY KEY,
17753     type           INT              NOT NULL REFERENCES acq.claim_event_type
17754                                              DEFERRABLE INITIALLY DEFERRED,
17755     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
17756                                              DEFERRABLE INITIALLY DEFERRED,
17757     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
17758     creator        INT              NOT NULL REFERENCES actor.usr
17759                                              DEFERRABLE INITIALLY DEFERRED,
17760     note           TEXT
17761 );
17762
17763 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
17764
17765 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
17766
17767 -- now what about the auditor.*_lifecycle views??
17768
17769 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17770     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
17771 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17772     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
17773 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
17774 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
17775
17776 CREATE TABLE asset.call_number_class (
17777     id             bigserial     PRIMARY KEY,
17778     name           TEXT          NOT NULL,
17779     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
17780     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17781 );
17782
17783 COMMENT ON TABLE asset.call_number_class IS $$
17784 Defines the call number normalization database functions in the "normalizer"
17785 column and the tag/subfield combinations to use to lookup the call number in
17786 the "field" column for a given classification scheme. Tag/subfield combinations
17787 are delimited by commas.
17788 $$;
17789
17790 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
17791     ('Generic', 'asset.label_normalizer_generic'),
17792     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
17793     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
17794 ;
17795
17796 -- Generic fields
17797 UPDATE asset.call_number_class
17798     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17799     WHERE id = 1
17800 ;
17801
17802 -- Dewey fields
17803 UPDATE asset.call_number_class
17804     SET field = '080ab,082ab'
17805     WHERE id = 2
17806 ;
17807
17808 -- LC fields
17809 UPDATE asset.call_number_class
17810     SET field = '050ab,055ab'
17811     WHERE id = 3
17812 ;
17813  
17814 ALTER TABLE asset.call_number
17815         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
17816                 REFERENCES asset.call_number_class(id)
17817                 DEFERRABLE INITIALLY DEFERRED;
17818
17819 ALTER TABLE asset.call_number
17820         ADD COLUMN label_sortkey TEXT;
17821
17822 CREATE INDEX asset_call_number_label_sortkey
17823         ON asset.call_number(oils_text_as_bytea(label_sortkey));
17824
17825 ALTER TABLE auditor.asset_call_number_history
17826         ADD COLUMN label_class BIGINT;
17827
17828 ALTER TABLE auditor.asset_call_number_history
17829         ADD COLUMN label_sortkey TEXT;
17830
17831 -- Pick up the new columns in dependent views
17832
17833 DROP VIEW auditor.asset_call_number_lifecycle;
17834
17835 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17836
17837 DROP VIEW auditor.asset_call_number_lifecycle;
17838
17839 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17840
17841 DROP VIEW IF EXISTS stats.fleshed_call_number;
17842
17843 CREATE VIEW stats.fleshed_call_number AS
17844         SELECT  cn.*,
17845             CAST(cn.create_date AS DATE) AS create_date_day,
17846         CAST(cn.edit_date AS DATE) AS edit_date_day,
17847         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
17848         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
17849             rd.item_lang,
17850                 rd.item_type,
17851                 rd.item_form
17852         FROM    asset.call_number cn
17853                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
17854
17855 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
17856 DECLARE
17857     sortkey        TEXT := '';
17858 BEGIN
17859     sortkey := NEW.label_sortkey;
17860
17861     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
17862        quote_literal( NEW.label ) || ')'
17863        FROM asset.call_number_class acnc
17864        WHERE acnc.id = NEW.label_class
17865        INTO sortkey;
17866
17867     NEW.label_sortkey = sortkey;
17868
17869     RETURN NEW;
17870 END;
17871 $func$ LANGUAGE PLPGSQL;
17872
17873 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
17874     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
17875     # thus could probably be considered a derived work, although nothing was
17876     # directly copied - but to err on the safe side of providing attribution:
17877     # Copyright (C) 2007 LibLime
17878     # Licensed under the GPL v2 or later
17879
17880     use strict;
17881     use warnings;
17882
17883     # Converts the callnumber to uppercase
17884     # Strips spaces from start and end of the call number
17885     # Converts anything other than letters, digits, and periods into underscores
17886     # Collapses multiple underscores into a single underscore
17887     my $callnum = uc(shift);
17888     $callnum =~ s/^\s//g;
17889     $callnum =~ s/\s$//g;
17890     $callnum =~ s/[^A-Z0-9_.]/_/g;
17891     $callnum =~ s/_{2,}/_/g;
17892
17893     return $callnum;
17894 $func$ LANGUAGE PLPERLU;
17895
17896 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
17897     # Derived from the Koha C4::ClassSortRoutine::Dewey module
17898     # Copyright (C) 2007 LibLime
17899     # Licensed under the GPL v2 or later
17900
17901     use strict;
17902     use warnings;
17903
17904     my $init = uc(shift);
17905     $init =~ s/^\s+//;
17906     $init =~ s/\s+$//;
17907     $init =~ s!/!!g;
17908     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
17909     my @tokens = split /\.|\s+/, $init;
17910     my $digit_group_count = 0;
17911     for (my $i = 0; $i <= $#tokens; $i++) {
17912         if ($tokens[$i] =~ /^\d+$/) {
17913             $digit_group_count++;
17914             if (2 == $digit_group_count) {
17915                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
17916                 $tokens[$i] =~ tr/ /0/;
17917             }
17918         }
17919     }
17920     my $key = join("_", @tokens);
17921     $key =~ s/[^\p{IsAlnum}_]//g;
17922
17923     return $key;
17924
17925 $func$ LANGUAGE PLPERLU;
17926
17927 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
17928     use strict;
17929     use warnings;
17930
17931     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
17932     # The author hopes to upload it to CPAN some day, which would make our lives easier
17933     use Library::CallNumber::LC;
17934
17935     my $callnum = Library::CallNumber::LC->new(shift);
17936     return $callnum->normalize();
17937
17938 $func$ LANGUAGE PLPERLU;
17939
17940 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$
17941 DECLARE
17942     ans RECORD;
17943     trans INT;
17944 BEGIN
17945     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;
17946
17947     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
17948         RETURN QUERY
17949         SELECT  ans.depth,
17950                 ans.id,
17951                 COUNT( av.id ),
17952                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
17953                 COUNT( av.id ),
17954                 trans
17955           FROM
17956                 actor.org_unit_descendants(ans.id) d
17957                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
17958                 JOIN asset.copy cp ON (cp.id = av.id)
17959           GROUP BY 1,2,6;
17960
17961         IF NOT FOUND THEN
17962             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
17963         END IF;
17964
17965     END LOOP;
17966
17967     RETURN;
17968 END;
17969 $f$ LANGUAGE PLPGSQL;
17970
17971 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$
17972 DECLARE
17973     ans RECORD;
17974     trans INT;
17975 BEGIN
17976     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;
17977
17978     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
17979         RETURN QUERY
17980         SELECT  -1,
17981                 ans.id,
17982                 COUNT( av.id ),
17983                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
17984                 COUNT( av.id ),
17985                 trans
17986           FROM
17987                 actor.org_unit_descendants(ans.id) d
17988                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
17989                 JOIN asset.copy cp ON (cp.id = av.id)
17990           GROUP BY 1,2,6;
17991
17992         IF NOT FOUND THEN
17993             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
17994         END IF;
17995
17996     END LOOP;
17997
17998     RETURN;
17999 END;
18000 $f$ LANGUAGE PLPGSQL;
18001
18002 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$
18003 DECLARE
18004     ans RECORD;
18005     trans INT;
18006 BEGIN
18007     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;
18008
18009     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
18010         RETURN QUERY
18011         SELECT  ans.depth,
18012                 ans.id,
18013                 COUNT( cp.id ),
18014                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18015                 COUNT( cp.id ),
18016                 trans
18017           FROM
18018                 actor.org_unit_descendants(ans.id) d
18019                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18020                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18021           GROUP BY 1,2,6;
18022
18023         IF NOT FOUND THEN
18024             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18025         END IF;
18026
18027     END LOOP;
18028
18029     RETURN;
18030 END;
18031 $f$ LANGUAGE PLPGSQL;
18032
18033 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$
18034 DECLARE
18035     ans RECORD;
18036     trans INT;
18037 BEGIN
18038     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;
18039
18040     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18041         RETURN QUERY
18042         SELECT  -1,
18043                 ans.id,
18044                 COUNT( cp.id ),
18045                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18046                 COUNT( cp.id ),
18047                 trans
18048           FROM
18049                 actor.org_unit_descendants(ans.id) d
18050                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18051                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18052           GROUP BY 1,2,6;
18053
18054         IF NOT FOUND THEN
18055             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18056         END IF;
18057
18058     END LOOP;
18059
18060     RETURN;
18061 END;
18062 $f$ LANGUAGE PLPGSQL;
18063
18064 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$
18065 BEGIN
18066     IF staff IS TRUE THEN
18067         IF place > 0 THEN
18068             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
18069         ELSE
18070             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
18071         END IF;
18072     ELSE
18073         IF place > 0 THEN
18074             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18075         ELSE
18076             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18077         END IF;
18078     END IF;
18079
18080     RETURN;
18081 END;
18082 $f$ LANGUAGE PLPGSQL;
18083
18084 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$
18085 DECLARE
18086     ans RECORD;
18087     trans INT;
18088 BEGIN
18089     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;
18090
18091     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
18092         RETURN QUERY
18093         SELECT  ans.depth,
18094                 ans.id,
18095                 COUNT( av.id ),
18096                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18097                 COUNT( av.id ),
18098                 trans
18099           FROM
18100                 actor.org_unit_descendants(ans.id) d
18101                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18102                 JOIN asset.copy cp ON (cp.id = av.id)
18103                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18104           GROUP BY 1,2,6;
18105
18106         IF NOT FOUND THEN
18107             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18108         END IF;
18109
18110     END LOOP;
18111
18112     RETURN;
18113 END;
18114 $f$ LANGUAGE PLPGSQL;
18115
18116 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$
18117 DECLARE
18118     ans RECORD;
18119     trans INT;
18120 BEGIN
18121     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;
18122
18123     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18124         RETURN QUERY
18125         SELECT  -1,
18126                 ans.id,
18127                 COUNT( av.id ),
18128                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18129                 COUNT( av.id ),
18130                 trans
18131           FROM
18132                 actor.org_unit_descendants(ans.id) d
18133                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18134                 JOIN asset.copy cp ON (cp.id = av.id)
18135                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18136           GROUP BY 1,2,6;
18137
18138         IF NOT FOUND THEN
18139             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18140         END IF;
18141
18142     END LOOP;
18143
18144     RETURN;
18145 END;
18146 $f$ LANGUAGE PLPGSQL;
18147
18148 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$
18149 DECLARE
18150     ans RECORD;
18151     trans INT;
18152 BEGIN
18153     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;
18154
18155     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
18156         RETURN QUERY
18157         SELECT  ans.depth,
18158                 ans.id,
18159                 COUNT( cp.id ),
18160                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18161                 COUNT( cp.id ),
18162                 trans
18163           FROM
18164                 actor.org_unit_descendants(ans.id) d
18165                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18166                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18167                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18168           GROUP BY 1,2,6;
18169
18170         IF NOT FOUND THEN
18171             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18172         END IF;
18173
18174     END LOOP;
18175
18176     RETURN;
18177 END;
18178 $f$ LANGUAGE PLPGSQL;
18179
18180 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$
18181 DECLARE
18182     ans RECORD;
18183     trans INT;
18184 BEGIN
18185     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;
18186
18187     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18188         RETURN QUERY
18189         SELECT  -1,
18190                 ans.id,
18191                 COUNT( cp.id ),
18192                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18193                 COUNT( cp.id ),
18194                 trans
18195           FROM
18196                 actor.org_unit_descendants(ans.id) d
18197                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18198                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18199                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18200           GROUP BY 1,2,6;
18201
18202         IF NOT FOUND THEN
18203             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18204         END IF;
18205
18206     END LOOP;
18207
18208     RETURN;
18209 END;
18210 $f$ LANGUAGE PLPGSQL;
18211
18212 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$
18213 BEGIN
18214     IF staff IS TRUE THEN
18215         IF place > 0 THEN
18216             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18217         ELSE
18218             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18219         END IF;
18220     ELSE
18221         IF place > 0 THEN
18222             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18223         ELSE
18224             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18225         END IF;
18226     END IF;
18227
18228     RETURN;
18229 END;
18230 $f$ LANGUAGE PLPGSQL;
18231
18232 -- No transaction is required
18233
18234 -- Triggers on the vandelay.queued_*_record tables delete entries from
18235 -- the associated vandelay.queued_*_record_attr tables based on the record's
18236 -- ID; create an index on that column to avoid sequential scans for each
18237 -- queued record that is deleted
18238 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18239 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18240
18241 -- Avoid sequential scans for queue retrieval operations by providing an
18242 -- index on the queue column
18243 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18244 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18245
18246 -- Start picking up call number label prefixes and suffixes
18247 -- from asset.copy_location
18248 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18249 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18250
18251 DROP VIEW auditor.asset_copy_lifecycle;
18252
18253 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18254
18255 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18256
18257 -- Let's not break existing reports
18258 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18259 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18260
18261 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18262 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18263 SELECT  r.id,
18264     r.fingerprint,
18265     r.quality,
18266     r.tcn_source,
18267     r.tcn_value,
18268     FIRST(title.value) AS title,
18269     FIRST(author.value) AS author,
18270     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18271     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18272     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18273     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18274   FROM  biblio.record_entry r
18275     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18276     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18277     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18278     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18279     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18280     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18281   GROUP BY 1,2,3,4,5;
18282
18283 -- Correct the ISSN array definition for reporter.simple_record
18284
18285 CREATE OR REPLACE VIEW reporter.simple_record AS
18286 SELECT  r.id,
18287         s.metarecord,
18288         r.fingerprint,
18289         r.quality,
18290         r.tcn_source,
18291         r.tcn_value,
18292         title.value AS title,
18293         uniform_title.value AS uniform_title,
18294         author.value AS author,
18295         publisher.value AS publisher,
18296         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18297         series_title.value AS series_title,
18298         series_statement.value AS series_statement,
18299         summary.value AS summary,
18300         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18301         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18302         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18303         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18304         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18305         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18306         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18307         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
18308   FROM  biblio.record_entry r
18309         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18310         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18311         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18312         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18313         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18314         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18315         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18316         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18317         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')
18318         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18319         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18320   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18321
18322 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18323     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
18324 $$ LANGUAGE SQL;
18325
18326 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18327 BEGIN
18328     IF TG_OP = 'DELETE' THEN
18329         PERFORM reporter.simple_rec_delete(NEW.id);
18330     ELSE
18331         PERFORM reporter.simple_rec_update(NEW.id);
18332     END IF;
18333
18334     RETURN NEW;
18335 END;
18336 $func$ LANGUAGE PLPGSQL;
18337
18338 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
18339
18340 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18341
18342 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18343
18344 UPDATE config.org_unit_setting_type
18345     SET view_perm = (SELECT id FROM permission.perm_list
18346         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18347     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18348
18349 UPDATE config.org_unit_setting_type
18350     SET update_perm = (SELECT id FROM permission.perm_list
18351         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18352     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18353
18354 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18355     VALUES (
18356         'opac.fully_compressed_serial_holdings',
18357         'OPAC: Use fully compressed serial holdings',
18358         'Show fully compressed serial holdings for all libraries at and below
18359         the current context unit',
18360         'bool'
18361     );
18362
18363 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18364     use strict;
18365     use warnings;
18366
18367     use utf8;
18368     use MARC::Record;
18369     use MARC::File::XML (BinaryEncoding => 'UTF8');
18370     use UUID::Tiny ':std';
18371
18372     my $xml = shift() or return undef;
18373
18374     my $r;
18375
18376     # Prevent errors in XML parsing from blowing out ungracefully
18377     eval {
18378         $r = MARC::Record->new_from_xml( $xml );
18379         1;
18380     } or do {
18381        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18382     };
18383
18384     if (!$r) {
18385        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18386     }
18387
18388     # From http://www.loc.gov/standards/sourcelist/subject.html
18389     my $thes_code_map = {
18390         a => 'lcsh',
18391         b => 'lcshac',
18392         c => 'mesh',
18393         d => 'nal',
18394         k => 'cash',
18395         n => 'notapplicable',
18396         r => 'aat',
18397         s => 'sears',
18398         v => 'rvm',
18399     };
18400
18401     # Default to "No attempt to code" if the leader is horribly broken
18402     my $fixed_field = $r->field('008');
18403     my $thes_char = '|';
18404     if ($fixed_field) {
18405         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18406     }
18407
18408     my $thes_code = 'UNDEFINED';
18409
18410     if ($thes_char eq 'z') {
18411         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18412         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18413     } elsif ($thes_code_map->{$thes_char}) {
18414         $thes_code = $thes_code_map->{$thes_char};
18415     }
18416
18417     my $auth_txt = '';
18418     my $head = $r->field('1..');
18419     if ($head) {
18420         # Concatenate all of these subfields together, prefixed by their code
18421         # to prevent collisions along the lines of "Fiction, North Carolina"
18422         foreach my $sf ($head->subfields()) {
18423             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18424         }
18425     }
18426
18427     # Perhaps better to parameterize the spi and pass as a parameter
18428     $auth_txt =~ s/'//go;
18429
18430     if ($auth_txt) {
18431         my $result = spi_exec_query("SELECT public.naco_normalize('$auth_txt') AS norm_text");
18432         my $norm_txt = $result->{rows}[0]->{norm_text};
18433         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18434     }
18435
18436     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18437 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18438
18439 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18440 /**
18441 * Extract the authority heading, thesaurus, and NACO-normalized values
18442 * from an authority record. The primary purpose is to build a unique
18443 * index to defend against duplicated authority records from the same
18444 * thesaurus.
18445 */
18446 $$;
18447
18448 DROP INDEX authority.authority_record_unique_tcn;
18449 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18450 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18451
18452 ALTER TABLE acq.provider_contact
18453         ALTER COLUMN name SET NOT NULL;
18454
18455 ALTER TABLE actor.stat_cat
18456         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18457
18458 -- Recreate some foreign keys that were somehow dropped, probably
18459 -- by some kind of cascade from an inherited table:
18460
18461 ALTER TABLE action.reservation_transit_copy
18462         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
18463                 REFERENCES booking.resource(id)
18464                 ON DELETE CASCADE
18465                 DEFERRABLE INITIALLY DEFERRED,
18466         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
18467                 REFERENCES booking.reservation(id)
18468                 ON DELETE SET NULL
18469                 DEFERRABLE INITIALLY DEFERRED;
18470
18471 CREATE INDEX user_bucket_item_target_user_idx
18472         ON container.user_bucket_item ( target_user );
18473
18474 CREATE INDEX m_c_t_collector_idx
18475         ON money.collections_tracker ( collector );
18476
18477 CREATE INDEX aud_actor_usr_address_hist_id_idx
18478         ON auditor.actor_usr_address_history ( id );
18479
18480 CREATE INDEX aud_actor_usr_hist_id_idx
18481         ON auditor.actor_usr_history ( id );
18482
18483 CREATE INDEX aud_asset_cn_hist_creator_idx
18484         ON auditor.asset_call_number_history ( creator );
18485
18486 CREATE INDEX aud_asset_cn_hist_editor_idx
18487         ON auditor.asset_call_number_history ( editor );
18488
18489 CREATE INDEX aud_asset_cp_hist_creator_idx
18490         ON auditor.asset_copy_history ( creator );
18491
18492 CREATE INDEX aud_asset_cp_hist_editor_idx
18493         ON auditor.asset_copy_history ( editor );
18494
18495 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18496         ON auditor.biblio_record_entry_history ( creator );
18497
18498 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18499         ON auditor.biblio_record_entry_history ( editor );
18500
18501 CREATE TABLE action.hold_request_note (
18502
18503     id     BIGSERIAL PRIMARY KEY,
18504     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18505                               ON DELETE CASCADE
18506                               DEFERRABLE INITIALLY DEFERRED,
18507     title  TEXT      NOT NULL,
18508     body   TEXT      NOT NULL,
18509     slip   BOOL      NOT NULL DEFAULT FALSE,
18510     pub    BOOL      NOT NULL DEFAULT FALSE,
18511     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18512
18513 );
18514 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18515
18516 -- Tweak a constraint to add a CASCADE
18517
18518 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18519
18520 ALTER TABLE action.hold_notification
18521         ADD CONSTRAINT hold_notification_hold_fkey
18522                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18523                 ON DELETE CASCADE
18524                 DEFERRABLE INITIALLY DEFERRED;
18525
18526 CREATE TRIGGER asset_label_sortkey_trigger
18527     BEFORE UPDATE OR INSERT ON asset.call_number
18528     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18529
18530 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18531 RETURNS VOID AS $$
18532 --
18533 -- Delete expired circulation bucket items for all users that have
18534 -- a setting for patron.max_reading_list_interval.
18535 --
18536 DECLARE
18537     today        TIMESTAMP WITH TIME ZONE;
18538     threshold    TIMESTAMP WITH TIME ZONE;
18539         usr_setting  RECORD;
18540 BEGIN
18541         SELECT date_trunc( 'day', now() ) INTO today;
18542         --
18543         FOR usr_setting in
18544                 SELECT
18545                         usr,
18546                         value
18547                 FROM
18548                         actor.usr_setting
18549                 WHERE
18550                         name = 'patron.max_reading_list_interval'
18551         LOOP
18552                 --
18553                 -- Make sure the setting is a valid interval
18554                 --
18555                 BEGIN
18556                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18557                 EXCEPTION
18558                         WHEN OTHERS THEN
18559                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18560                                         usr_setting.usr, usr_setting.value;
18561                                 CONTINUE;
18562                 END;
18563                 --
18564                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18565                 --
18566         DELETE FROM container.copy_bucket_item
18567         WHERE
18568                 bucket IN
18569                 (
18570                     SELECT
18571                         id
18572                     FROM
18573                         container.copy_bucket
18574                     WHERE
18575                         owner = usr_setting.usr
18576                         AND btype = 'circ_history'
18577                 )
18578                 AND create_time < threshold;
18579         END LOOP;
18580         --
18581 END;
18582 $$ LANGUAGE plpgsql;
18583
18584 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18585 /*
18586  * Delete expired circulation bucket items for all users that have
18587  * a setting for patron.max_reading_list_interval.
18588 */
18589 $$;
18590
18591 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18592          ac_usr IN INTEGER
18593 ) RETURNS VOID AS $$
18594 --
18595 -- Delete old circulation bucket items for a specified user.
18596 -- "Old" means older than the interval specified by a
18597 -- user-level setting, if it is so specified.
18598 --
18599 DECLARE
18600     threshold TIMESTAMP WITH TIME ZONE;
18601 BEGIN
18602         -- Sanity check
18603         IF ac_usr IS NULL THEN
18604                 RETURN;
18605         END IF;
18606         -- Determine the threshold date that defines "old".  Subtract the
18607         -- interval from the system date, then truncate to midnight.
18608         SELECT
18609                 date_trunc( 
18610                         'day',
18611                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18612                 )
18613         INTO
18614                 threshold
18615         FROM
18616                 actor.usr_setting
18617         WHERE
18618                 usr = ac_usr
18619                 AND name = 'patron.max_reading_list_interval';
18620         --
18621         IF threshold is null THEN
18622                 -- No interval defined; don't delete anything
18623                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18624                 return;
18625         END IF;
18626         --
18627         -- RAISE NOTICE 'Date threshold: %', threshold;
18628         --
18629         -- Threshold found; do the delete
18630         delete from container.copy_bucket_item
18631         where
18632                 bucket in
18633                 (
18634                         select
18635                                 id
18636                         from
18637                                 container.copy_bucket
18638                         where
18639                                 owner = ac_usr
18640                                 and btype = 'circ_history'
18641                 )
18642                 and create_time < threshold;
18643         --
18644         RETURN;
18645 END;
18646 $$ LANGUAGE plpgsql;
18647
18648 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18649 /*
18650  * Delete old circulation bucket items for a specified user.
18651  * "Old" means older than the interval specified by a
18652  * user-level setting, if it is so specified.
18653 */
18654 $$;
18655
18656 CREATE OR REPLACE VIEW reporter.hold_request_record AS
18657 SELECT  id,
18658     target,
18659     hold_type,
18660     CASE
18661         WHEN hold_type = 'T'
18662             THEN target
18663         WHEN hold_type = 'I'
18664             THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
18665         WHEN hold_type = 'V'
18666             THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
18667         WHEN hold_type IN ('C','R','F')
18668             THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
18669         WHEN hold_type = 'M'
18670             THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
18671     END AS bib_record
18672   FROM  action.hold_request ahr;
18673
18674 UPDATE  metabib.rec_descriptor
18675   SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
18676         date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
18677
18678 -- Change some ints to bigints:
18679
18680 ALTER TABLE container.biblio_record_entry_bucket_item
18681         ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
18682
18683 ALTER TABLE vandelay.queued_bib_record
18684         ALTER COLUMN imported_as SET DATA TYPE bigint;
18685
18686 ALTER TABLE action.hold_copy_map
18687         ALTER COLUMN id SET DATA TYPE bigint;
18688
18689 -- Make due times get pushed to 23:59:59 on insert OR update
18690 DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
18691 CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
18692
18693 COMMIT;
18694
18695 -- Some operations go outside of the transaction, because they may
18696 -- legitimately fail.
18697
18698 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
18699 \qecho doesn't exist; ignore those errors if they occur.
18700
18701 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
18702
18703 ALTER TABLE auditor.action_hold_request_history
18704 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
18705
18706 ALTER TABLE auditor.action_hold_request_history
18707 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
18708
18709 \qecho Outside of the transaction: adding indexes that may or may not exist.
18710 \qecho If any of these CREATE INDEX statements fails because the index already
18711 \qecho exists, ignore the failure.
18712
18713 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
18714 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
18715 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
18716 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
18717 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
18718 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
18719 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
18720 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
18721 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
18722 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
18723 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
18724 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
18725 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
18726 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
18727 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
18728 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
18729 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
18730 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
18731 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
18732 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
18733 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
18734 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
18735 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
18736 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
18737 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
18738 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
18739 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
18740 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
18741 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
18742 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
18743 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
18744 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
18745 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
18746
18747 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
18748
18749 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
18750
18751 \qecho If the following CREATE INDEX fails, It will be necessary to do some
18752 \qecho data cleanup as described in the comments.
18753
18754 CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
18755     ON authority.record_entry (authority.normalize_heading(marc))
18756         WHERE deleted IS FALSE or deleted = FALSE;
18757
18758 -- If the unique index fails, uncomment the following to create
18759 -- a regular index that will help find the duplicates in a hurry:
18760 --CREATE INDEX by_heading_and_thesaurus
18761 --    ON authority.record_entry (authority.normalize_heading(marc))
18762 --    WHERE deleted IS FALSE or deleted = FALSE
18763 --;
18764
18765 -- Then find the duplicates like so to get an idea of how much
18766 -- pain you're looking at to clean things up:
18767 --SELECT id, authority.normalize_heading(marc)
18768 --    FROM authority.record_entry
18769 --    WHERE authority.normalize_heading(marc) IN (
18770 --        SELECT authority.normalize_heading(marc)
18771 --        FROM authority.record_entry
18772 --        GROUP BY authority.normalize_heading(marc)
18773 --        HAVING COUNT(*) > 1
18774 --    )
18775 --;
18776
18777 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
18778 -- statement succeeds, drop the temporary index to avoid unnecessary
18779 -- duplication:
18780 -- DROP INDEX authority.by_heading_and_thesaurus;
18781
18782 \qecho Upgrade script completed.