]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
Bring 1.6.1-2.0 upgrade script in line with reality
[Evergreen.git] / Open-ILS / src / sql / Pg / 1.6.1-2.0-upgrade-db.sql
1 -- Before starting the transaction: drop some constraints that
2 -- may or may not exist.
3
4 CREATE OR REPLACE FUNCTION oils_text_as_bytea (TEXT) RETURNS BYTEA AS $_$
5     SELECT CAST(REGEXP_REPLACE(UPPER($1), $$\\$$, $$\\\\$$, 'g') AS BYTEA);
6 $_$ LANGUAGE SQL IMMUTABLE;
7
8 DROP INDEX asset.asset_call_number_upper_label_id_owning_lib_idx;
9 CREATE INDEX asset_call_number_upper_label_id_owning_lib_idx ON asset.call_number (oils_text_as_bytea(label),id,owning_lib);
10
11 \qecho Before starting the transaction: drop some constraints.
12 \qecho If a DROP fails because the constraint doesn't exist, ignore the failure.
13
14 ALTER TABLE permission.grp_perm_map        DROP CONSTRAINT grp_perm_map_perm_fkey;
15 ALTER TABLE permission.usr_perm_map        DROP CONSTRAINT usr_perm_map_perm_fkey;
16 ALTER TABLE permission.usr_object_perm_map DROP CONSTRAINT usr_object_perm_map_perm_fkey;
17 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_or_record_once_per_owner;
18 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_once_per_owner;
19
20 \qecho Beginning the transaction now
21
22 BEGIN;
23
24 -- Highest-numbered individual upgrade script incorporated herein:
25
26 INSERT INTO config.upgrade_log (version) VALUES ('0445');
27
28 -- Remove some uses of the connectby() function from the tablefunc contrib module
29 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT, INT ) RETURNS SETOF actor.org_unit AS $$
30     WITH RECURSIVE descendant_depth AS (
31         SELECT  ou.id,
32                 ou.parent_ou,
33                 out.depth
34           FROM  actor.org_unit ou
35                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
36                 JOIN anscestor_depth ad ON (ad.id = ou.id)
37           WHERE ad.depth = $2
38             UNION ALL
39         SELECT  ou.id,
40                 ou.parent_ou,
41                 out.depth
42           FROM  actor.org_unit ou
43                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
44                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
45     ), anscestor_depth AS (
46         SELECT  ou.id,
47                 ou.parent_ou,
48                 out.depth
49           FROM  actor.org_unit ou
50                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
51           WHERE ou.id = $1
52             UNION ALL
53         SELECT  ou.id,
54                 ou.parent_ou,
55                 out.depth
56           FROM  actor.org_unit ou
57                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
58                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
59     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
60 $$ LANGUAGE SQL;
61
62 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT ) RETURNS SETOF actor.org_unit AS $$
63     WITH RECURSIVE descendant_depth AS (
64         SELECT  ou.id,
65                 ou.parent_ou,
66                 out.depth
67           FROM  actor.org_unit ou
68                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
69           WHERE ou.id = $1
70             UNION ALL
71         SELECT  ou.id,
72                 ou.parent_ou,
73                 out.depth
74           FROM  actor.org_unit ou
75                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
76                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
77     ), anscestor_depth AS (
78         SELECT  ou.id,
79                 ou.parent_ou,
80                 out.depth
81           FROM  actor.org_unit ou
82                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
83           WHERE ou.id = $1
84             UNION ALL
85         SELECT  ou.id,
86                 ou.parent_ou,
87                 out.depth
88           FROM  actor.org_unit ou
89                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
90                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
91     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
92 $$ LANGUAGE SQL;
93
94 CREATE OR REPLACE FUNCTION actor.org_unit_ancestors( INT ) RETURNS SETOF actor.org_unit AS $$
95     WITH RECURSIVE anscestor_depth AS (
96         SELECT  ou.id,
97                 ou.parent_ou
98           FROM  actor.org_unit ou
99           WHERE ou.id = $1
100             UNION ALL
101         SELECT  ou.id,
102                 ou.parent_ou
103           FROM  actor.org_unit ou
104                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
105     ) SELECT ou.* FROM actor.org_unit ou JOIN anscestor_depth USING (id);
106 $$ LANGUAGE SQL;
107
108 -- Support merge template buckets
109 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('template_merge','Template Merge Container');
110
111 -- Recreate one of the constraints that we just dropped,
112 -- under a different name:
113
114 ALTER TABLE booking.resource_type
115         ADD CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record);
116
117 -- Now upgrade permission.perm_list.  This is fairly complicated.
118
119 -- Add ON UPDATE CASCADE to some foreign keys so that, when we renumber the
120 -- permissions, the dependents will follow and stay in sync:
121
122 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
123     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
124
125 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_perm_map_perm_fkey FOREIGN KEY (perm)
126     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
127
128 ALTER TABLE permission.usr_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
129     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
130
131 UPDATE permission.perm_list
132     SET code = 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow'
133     WHERE code = 'UPDATE_ORG_UNIT_SETTING.global.credit.allow';
134
135 -- The following UPDATES were originally in an individual upgrade script, but should
136 -- no longer be necessary now that the foreign key has an ON UPDATE CASCADE clause.
137 -- We retain the UPDATES here, commented out, as historical relics.
138
139 -- UPDATE permission.grp_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
140 -- UPDATE permission.usr_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
141
142 -- Spelling correction
143 UPDATE permission.perm_list SET code = 'ADMIN_RECURRING_FINE_RULE' WHERE code = 'ADMIN_RECURING_FINE_RULE';
144
145 -- Now we engage in a Great Renumbering of the permissions in permission.perm_list,
146 -- in order to clean up accumulated cruft.
147
148 -- The first step is to establish some triggers so that, when we change the id of a permission,
149 -- the associated translations are updated accordingly.
150
151 CREATE OR REPLACE FUNCTION oils_i18n_update_apply(old_ident TEXT, new_ident TEXT, hint TEXT) RETURNS VOID AS $_$
152 BEGIN
153
154     EXECUTE $$
155         UPDATE  config.i18n_core
156           SET   identity_value = $$ || quote_literal( new_ident ) || $$ 
157           WHERE fq_field LIKE '$$ || hint || $$.%' 
158                 AND identity_value = $$ || quote_literal( old_ident ) || $$;$$;
159
160     RETURN;
161
162 END;
163 $_$ LANGUAGE PLPGSQL;
164
165 CREATE OR REPLACE FUNCTION oils_i18n_id_tracking(/* hint */) RETURNS TRIGGER AS $_$
166 BEGIN
167     PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
168     RETURN NEW;
169 END;
170 $_$ LANGUAGE PLPGSQL;
171
172 CREATE OR REPLACE FUNCTION oils_i18n_code_tracking(/* hint */) RETURNS TRIGGER AS $_$
173 BEGIN
174     PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
175     RETURN NEW;
176 END;
177 $_$ LANGUAGE PLPGSQL;
178
179
180 CREATE TRIGGER maintain_perm_i18n_tgr
181     AFTER UPDATE ON permission.perm_list
182     FOR EACH ROW EXECUTE PROCEDURE oils_i18n_id_tracking('ppl');
183
184 -- Next, create a new table as a convenience for sloshing data back and forth,
185 -- and for recording which permission went where.  It looks just like
186 -- permission.perm_list, but with two extra columns: one for the old id, and one to
187 -- distinguish between predefined permissions and non-predefined permissions.
188
189 -- This table is, in effect, a temporary table, because we can drop it once the
190 -- upgrade is complete.  It is not technically temporary as far as PostgreSQL is
191 -- concerned, because we don't want it to disappear at the end of the session.
192 -- We keep it around so that we have a map showing the old id and the new id for
193 -- each permission.  However there is no IDL entry for it, nor is it defined
194 -- in the base sql files.
195
196 CREATE TABLE permission.temp_perm (
197         id          INT        PRIMARY KEY,
198         code        TEXT       UNIQUE,
199         description TEXT,
200         old_id      INT,
201         predefined  BOOL       NOT NULL DEFAULT TRUE
202 );
203
204 -- Populate the temp table with a definitive set of predefined permissions,
205 -- hard-coding the ids.
206
207 -- The first set of permissions is derived from the database, as loaded in a
208 -- loaded 1.6.1 database, plus a few changes previously applied in this upgrade
209 -- script.  The second set is derived from the IDL -- permissions that are referenced
210 -- in <permacrud> elements but not defined in the database.
211
212 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( -1, 'EVERYTHING',
213      '' );
214 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 1, 'OPAC_LOGIN',
215      'Allow a user to log in to the OPAC' );
216 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 2, 'STAFF_LOGIN',
217      'Allow a user to log in to the staff client' );
218 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 3, 'MR_HOLDS',
219      'Allow a user to create a metarecord holds' );
220 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 4, 'TITLE_HOLDS',
221      'Allow a user to place a hold at the title level' );
222 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 5, 'VOLUME_HOLDS',
223      'Allow a user to place a volume level hold' );
224 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 6, 'COPY_HOLDS',
225      'Allow a user to place a hold on a specific copy' );
226 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 7, 'REQUEST_HOLDS',
227      '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)' );
228 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 8, 'REQUEST_HOLDS_OVERRIDE',
229      '* no longer applicable' );
230 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 9, 'VIEW_HOLD',
231      'Allow a user to view another user''s holds' );
232 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 10, 'DELETE_HOLDS',
233      '* no longer applicable' );
234 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 11, 'UPDATE_HOLD',
235      'Allow a user to update another user''s hold' );
236 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 12, 'RENEW_CIRC',
237      'Allow a user to renew items' );
238 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 13, 'VIEW_USER_FINES_SUMMARY',
239      'Allow a user to view bill details' );
240 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 14, 'VIEW_USER_TRANSACTIONS',
241      'Allow a user to see another user''s grocery or circulation transactions in the Bills Interface; duplicate of VIEW_TRANSACTION' );
242 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 15, 'UPDATE_MARC',
243      'Allow a user to edit a MARC record' );
244 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 16, 'CREATE_MARC',
245      'Allow a user to create new MARC records' );
246 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 17, 'IMPORT_MARC',
247      'Allow a user to import a MARC record via the Z39.50 interface' );
248 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 18, 'CREATE_VOLUME',
249      'Allow a user to create a volume' );
250 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 19, 'UPDATE_VOLUME',
251      '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.' );
252 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 20, 'DELETE_VOLUME',
253      'Allow a user to delete a volume' );
254 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 21, 'CREATE_COPY',
255      'Allow a user to create a new copy object' );
256 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 22, 'UPDATE_COPY',
257      'Allow a user to edit a copy' );
258 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 23, 'DELETE_COPY',
259      'Allow a user to delete a copy' );
260 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 24, 'RENEW_HOLD_OVERRIDE',
261      'Allow a user to continue to renew an item even if it is required for a hold' );
262 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 25, 'CREATE_USER',
263      'Allow a user to create another user' );
264 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 26, 'UPDATE_USER',
265      'Allow a user to edit a user''s record' );
266 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 27, 'DELETE_USER',
267      'Allow a user to mark a user as deleted' );
268 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 28, 'VIEW_USER',
269      'Allow a user to view another user''s Patron Record' );
270 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 29, 'COPY_CHECKIN',
271      'Allow a user to check in a copy' );
272 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 30, 'CREATE_TRANSIT',
273      'Allow a user to place an item in transit' );
274 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 31, 'VIEW_PERMISSION',
275      'Allow a user to view user permissions within the user permissions editor' );
276 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 32, 'CHECKIN_BYPASS_HOLD_FULFILL',
277      '* no longer applicable' );
278 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 33, 'CREATE_PAYMENT',
279      'Allow a user to record payments in the Billing Interface' );
280 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 34, 'SET_CIRC_LOST',
281      'Allow a user to mark an item as ''lost''' );
282 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 35, 'SET_CIRC_MISSING',
283      'Allow a user to mark an item as ''missing''' );
284 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 36, 'SET_CIRC_CLAIMS_RETURNED',
285      'Allow a user to mark an item as ''claims returned''' );
286 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 37, 'CREATE_TRANSACTION',
287      'Allow a user to create a new billable transaction' );
288 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 38, 'VIEW_TRANSACTION',
289      'Allow a user may view another user''s transactions' );
290 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 39, 'CREATE_BILL',
291      'Allow a user to create a new bill on a transaction' );
292 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 40, 'VIEW_CONTAINER',
293      'Allow a user to view another user''s containers (buckets)' );
294 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 41, 'CREATE_CONTAINER',
295      'Allow a user to create a new container for another user' );
296 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 42, 'UPDATE_ORG_UNIT',
297      'Allow a user to change the settings for an organization unit' );
298 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 43, 'VIEW_CIRCULATIONS',
299      'Allow a user to see what another user has checked out' );
300 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 44, 'DELETE_CONTAINER',
301      'Allow a user to delete another user''s container' );
302 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 45, 'CREATE_CONTAINER_ITEM',
303      'Allow a user to create a container item for another user' );
304 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 46, 'CREATE_USER_GROUP_LINK',
305      'Allow a user to add other users to permission groups' );
306 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 47, 'REMOVE_USER_GROUP_LINK',
307      'Allow a user to remove other users from permission groups' );
308 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 48, 'VIEW_PERM_GROUPS',
309      'Allow a user to view other users'' permission groups' );
310 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 49, 'VIEW_PERMIT_CHECKOUT',
311      'Allow a user to determine whether another user can check out an item' );
312 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 50, 'UPDATE_BATCH_COPY',
313      'Allow a user to edit copies in batch' );
314 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 51, 'CREATE_PATRON_STAT_CAT',
315      'User may create a new patron statistical category' );
316 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 52, 'CREATE_COPY_STAT_CAT',
317      'User may create a copy statistical category' );
318 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 53, 'CREATE_PATRON_STAT_CAT_ENTRY',
319      'User may create an entry in a patron statistical category' );
320 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 54, 'CREATE_COPY_STAT_CAT_ENTRY',
321      'User may create an entry in a copy statistical category' );
322 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 55, 'UPDATE_PATRON_STAT_CAT',
323      'User may update a patron statistical category' );
324 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 56, 'UPDATE_COPY_STAT_CAT',
325      'User may update a copy statistical category' );
326 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 57, 'UPDATE_PATRON_STAT_CAT_ENTRY',
327      'User may update an entry in a patron statistical category' );
328 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 58, 'UPDATE_COPY_STAT_CAT_ENTRY',
329      'User may update an entry in a copy statistical category' );
330 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 59, 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
331      'User may link another user to an entry in a statistical category' );
332 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 60, 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
333      'User may link a copy to an entry in a statistical category' );
334 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 61, 'DELETE_PATRON_STAT_CAT',
335      'User may delete a patron statistical category' );
336 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 62, 'DELETE_COPY_STAT_CAT',
337      'User may delete a copy statistical category' );
338 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 63, 'DELETE_PATRON_STAT_CAT_ENTRY',
339      'User may delete an entry from a patron statistical category' );
340 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 64, 'DELETE_COPY_STAT_CAT_ENTRY',
341      'User may delete an entry from a copy statistical category' );
342 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 65, 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
343      'User may delete a patron statistical category entry map' );
344 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 66, 'DELETE_COPY_STAT_CAT_ENTRY_MAP',
345      'User may delete a copy statistical category entry map' );
346 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 67, 'CREATE_NON_CAT_TYPE',
347      'Allow a user to create a new non-cataloged item type' );
348 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 68, 'UPDATE_NON_CAT_TYPE',
349      'Allow a user to update a non-cataloged item type' );
350 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 69, 'CREATE_IN_HOUSE_USE',
351      'Allow a user to create a new in-house-use ' );
352 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 70, 'COPY_CHECKOUT',
353      'Allow a user to check out a copy' );
354 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 71, 'CREATE_COPY_LOCATION',
355      'Allow a user to create a new copy location' );
356 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 72, 'UPDATE_COPY_LOCATION',
357      'Allow a user to update a copy location' );
358 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 73, 'DELETE_COPY_LOCATION',
359      'Allow a user to delete a copy location' );
360 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 74, 'CREATE_COPY_TRANSIT',
361      'Allow a user to create a transit_copy object for transiting a copy' );
362 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 75, 'COPY_TRANSIT_RECEIVE',
363      'Allow a user to close out a transit on a copy' );
364 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 76, 'VIEW_HOLD_PERMIT',
365      'Allow a user to see if another user has permission to place a hold on a given copy' );
366 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 77, 'VIEW_COPY_CHECKOUT_HISTORY',
367      'Allow a user to view which users have checked out a given copy' );
368 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 78, 'REMOTE_Z3950_QUERY',
369      'Allow a user to perform Z39.50 queries against remote servers' );
370 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 79, 'REGISTER_WORKSTATION',
371      'Allow a user to register a new workstation' );
372 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 80, 'VIEW_COPY_NOTES',
373      'Allow a user to view all notes attached to a copy' );
374 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 81, 'VIEW_VOLUME_NOTES',
375      'Allow a user to view all notes attached to a volume' );
376 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 82, 'VIEW_TITLE_NOTES',
377      'Allow a user to view all notes attached to a title' );
378 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 83, 'CREATE_COPY_NOTE',
379      'Allow a user to create a new copy note' );
380 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 84, 'CREATE_VOLUME_NOTE',
381      'Allow a user to create a new volume note' );
382 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 85, 'CREATE_TITLE_NOTE',
383      'Allow a user to create a new title note' );
384 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 86, 'DELETE_COPY_NOTE',
385      'Allow a user to delete another user''s copy notes' );
386 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 87, 'DELETE_VOLUME_NOTE',
387      'Allow a user to delete another user''s volume note' );
388 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 88, 'DELETE_TITLE_NOTE',
389      'Allow a user to delete another user''s title note' );
390 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 89, 'UPDATE_CONTAINER',
391      'Allow a user to update another user''s container' );
392 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 90, 'CREATE_MY_CONTAINER',
393      'Allow a user to create a container for themselves' );
394 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 91, 'VIEW_HOLD_NOTIFICATION',
395      'Allow a user to view notifications attached to a hold' );
396 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 92, 'CREATE_HOLD_NOTIFICATION',
397      'Allow a user to create new hold notifications' );
398 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 93, 'UPDATE_ORG_SETTING',
399      'Allow a user to update an organization unit setting' );
400 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 94, 'OFFLINE_UPLOAD',
401      'Allow a user to upload an offline script' );
402 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 95, 'OFFLINE_VIEW',
403      'Allow a user to view uploaded offline script information' );
404 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 96, 'OFFLINE_EXECUTE',
405      'Allow a user to execute an offline script batch' );
406 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 97, 'CIRC_OVERRIDE_DUE_DATE',
407      'Allow a user to change the due date on an item to any date' );
408 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 98, 'CIRC_PERMIT_OVERRIDE',
409      'Allow a user to bypass the circulation permit call for check out' );
410 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 99, 'COPY_IS_REFERENCE.override',
411      'Allow a user to override the copy_is_reference event' );
412 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 100, 'VOID_BILLING',
413      'Allow a user to void a bill' );
414 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 101, 'CIRC_CLAIMS_RETURNED.override',
415      'Allow a user to check in or check out an item that has a status of ''claims returned''' );
416 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 102, 'COPY_BAD_STATUS.override',
417      'Allow a user to check out an item in a non-circulatable status' );
418 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 103, 'COPY_ALERT_MESSAGE.override',
419      'Allow a user to check in/out an item that has an alert message' );
420 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 104, 'COPY_STATUS_LOST.override',
421      'Allow a user to remove the lost status from a copy' );
422 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 105, 'COPY_STATUS_MISSING.override',
423      'Allow a user to change the missing status on a copy' );
424 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 106, 'ABORT_TRANSIT',
425      'Allow a user to abort a copy transit if the user is at the transit destination or source' );
426 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 107, 'ABORT_REMOTE_TRANSIT',
427      'Allow a user to abort a copy transit if the user is not at the transit source or dest' );
428 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 108, 'VIEW_ZIP_DATA',
429      'Allow a user to query the ZIP code data method' );
430 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 109, 'CANCEL_HOLDS',
431      'Allow a user to cancel holds' );
432 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 110, 'CREATE_DUPLICATE_HOLDS',
433      'Allow a user to create duplicate holds (two or more holds on the same title)' );
434 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 111, 'actor.org_unit.closed_date.delete',
435      'Allow a user to remove a closed date interval for a given location' );
436 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 112, 'actor.org_unit.closed_date.update',
437      'Allow a user to update a closed date interval for a given location' );
438 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 113, 'actor.org_unit.closed_date.create',
439      'Allow a user to create a new closed date for a location' );
440 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 114, 'DELETE_NON_CAT_TYPE',
441      'Allow a user to delete a non cataloged type' );
442 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 115, 'money.collections_tracker.create',
443      'Allow a user to put someone into collections' );
444 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 116, 'money.collections_tracker.delete',
445      'Allow a user to remove someone from collections' );
446 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 117, 'BAR_PATRON',
447      'Allow a user to bar a patron' );
448 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 118, 'UNBAR_PATRON',
449      'Allow a user to un-bar a patron' );
450 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 119, 'DELETE_WORKSTATION',
451      'Allow a user to remove an existing workstation so a new one can replace it' );
452 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 120, 'group_application.user',
453      'Allow a user to add/remove users to/from the "User" group' );
454 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 121, 'group_application.user.patron',
455      'Allow a user to add/remove users to/from the "Patron" group' );
456 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 122, 'group_application.user.staff',
457      'Allow a user to add/remove users to/from the "Staff" group' );
458 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 123, 'group_application.user.staff.circ',
459      'Allow a user to add/remove users to/from the "Circulator" group' );
460 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 124, 'group_application.user.staff.cat',
461      'Allow a user to add/remove users to/from the "Cataloger" group' );
462 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 125, 'group_application.user.staff.admin.global_admin',
463      'Allow a user to add/remove users to/from the "GlobalAdmin" group' );
464 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 126, 'group_application.user.staff.admin.local_admin',
465      'Allow a user to add/remove users to/from the "LocalAdmin" group' );
466 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 127, 'group_application.user.staff.admin.lib_manager',
467      'Allow a user to add/remove users to/from the "LibraryManager" group' );
468 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 128, 'group_application.user.staff.cat.cat1',
469      'Allow a user to add/remove users to/from the "Cat1" group' );
470 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 129, 'group_application.user.staff.supercat',
471      'Allow a user to add/remove users to/from the "Supercat" group' );
472 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 130, 'group_application.user.sip_client',
473      'Allow a user to add/remove users to/from the "SIP-Client" group' );
474 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 131, 'group_application.user.vendor',
475      'Allow a user to add/remove users to/from the "Vendor" group' );
476 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 132, 'ITEM_AGE_PROTECTED.override',
477      'Allow a user to place a hold on an age-protected item' );
478 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 133, 'MAX_RENEWALS_REACHED.override',
479      'Allow a user to renew an item past the maximum renewal count' );
480 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 134, 'PATRON_EXCEEDS_CHECKOUT_COUNT.override',
481      'Allow staff to override checkout count failure' );
482 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 135, 'PATRON_EXCEEDS_OVERDUE_COUNT.override',
483      'Allow staff to override overdue count failure' );
484 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 136, 'PATRON_EXCEEDS_FINES.override',
485      'Allow staff to override fine amount checkout failure' );
486 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 137, 'CIRC_EXCEEDS_COPY_RANGE.override',
487      'Allow staff to override circulation copy range failure' );
488 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 138, 'ITEM_ON_HOLDS_SHELF.override',
489      'Allow staff to override item on holds shelf failure' );
490 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 139, 'COPY_NOT_AVAILABLE.override',
491      'Allow staff to force checkout of Missing/Lost type items' );
492 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 140, 'HOLD_EXISTS.override',
493      'Allow a user to place multiple holds on a single title' );
494 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 141, 'RUN_REPORTS',
495      'Allow a user to run reports' );
496 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 142, 'SHARE_REPORT_FOLDER',
497      'Allow a user to share report his own folders' );
498 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 143, 'VIEW_REPORT_OUTPUT',
499      'Allow a user to view report output' );
500 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 144, 'COPY_CIRC_NOT_ALLOWED.override',
501      'Allow a user to checkout an item that is marked as non-circ' );
502 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 145, 'DELETE_CONTAINER_ITEM',
503      'Allow a user to delete an item out of another user''s container' );
504 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 146, 'ASSIGN_WORK_ORG_UNIT',
505      'Allow a staff member to define where another staff member has their permissions' );
506 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 147, 'CREATE_FUNDING_SOURCE',
507      'Allow a user to create a new funding source' );
508 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 148, 'DELETE_FUNDING_SOURCE',
509      'Allow a user to delete a funding source' );
510 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 149, 'VIEW_FUNDING_SOURCE',
511      'Allow a user to view a funding source' );
512 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 150, 'UPDATE_FUNDING_SOURCE',
513      'Allow a user to update a funding source' );
514 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 151, 'CREATE_FUND',
515      'Allow a user to create a new fund' );
516 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 152, 'DELETE_FUND',
517      'Allow a user to delete a fund' );
518 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 153, 'VIEW_FUND',
519      'Allow a user to view a fund' );
520 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 154, 'UPDATE_FUND',
521      'Allow a user to update a fund' );
522 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 155, 'CREATE_FUND_ALLOCATION',
523      'Allow a user to create a new fund allocation' );
524 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 156, 'DELETE_FUND_ALLOCATION',
525      'Allow a user to delete a fund allocation' );
526 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 157, 'VIEW_FUND_ALLOCATION',
527      'Allow a user to view a fund allocation' );
528 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 158, 'UPDATE_FUND_ALLOCATION',
529      'Allow a user to update a fund allocation' );
530 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 159, 'GENERAL_ACQ',
531      'Lowest level permission required to access the ACQ interface' );
532 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 160, 'CREATE_PROVIDER',
533      'Allow a user to create a new provider' );
534 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 161, 'DELETE_PROVIDER',
535      'Allow a user to delate a provider' );
536 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 162, 'VIEW_PROVIDER',
537      'Allow a user to view a provider' );
538 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 163, 'UPDATE_PROVIDER',
539      'Allow a user to update a provider' );
540 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 164, 'ADMIN_FUNDING_SOURCE',
541      'Allow a user to create/view/update/delete a funding source' );
542 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 165, 'ADMIN_FUND',
543      '(Deprecated) Allow a user to create/view/update/delete a fund' );
544 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 166, 'MANAGE_FUNDING_SOURCE',
545      'Allow a user to view/credit/debit a funding source' );
546 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 167, 'MANAGE_FUND',
547      'Allow a user to view/credit/debit a fund' );
548 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 168, 'CREATE_PICKLIST',
549      'Allows a user to create a picklist' );
550 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 169, 'ADMIN_PROVIDER',
551      'Allow a user to create/view/update/delete a provider' );
552 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 170, 'MANAGE_PROVIDER',
553      'Allow a user to view and purchase from a provider' );
554 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 171, 'VIEW_PICKLIST',
555      'Allow a user to view another users picklist' );
556 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 172, 'DELETE_RECORD',
557      'Allow a staff member to directly remove a bibliographic record' );
558 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 173, 'ADMIN_CURRENCY_TYPE',
559      'Allow a user to create/view/update/delete a currency_type' );
560 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 174, 'MARK_BAD_DEBT',
561      'Allow a user to mark a transaction as bad (unrecoverable) debt' );
562 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 175, 'VIEW_BILLING_TYPE',
563      'Allow a user to view billing types' );
564 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 176, 'MARK_ITEM_AVAILABLE',
565      'Allow a user to mark an item status as ''available''' );
566 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 177, 'MARK_ITEM_CHECKED_OUT',
567      'Allow a user to mark an item status as ''checked out''' );
568 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 178, 'MARK_ITEM_BINDERY',
569      'Allow a user to mark an item status as ''bindery''' );
570 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 179, 'MARK_ITEM_LOST',
571      'Allow a user to mark an item status as ''lost''' );
572 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 180, 'MARK_ITEM_MISSING',
573      'Allow a user to mark an item status as ''missing''' );
574 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 181, 'MARK_ITEM_IN_PROCESS',
575      'Allow a user to mark an item status as ''in process''' );
576 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 182, 'MARK_ITEM_IN_TRANSIT',
577      'Allow a user to mark an item status as ''in transit''' );
578 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 183, 'MARK_ITEM_RESHELVING',
579      'Allow a user to mark an item status as ''reshelving''' );
580 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 184, 'MARK_ITEM_ON_HOLDS_SHELF',
581      'Allow a user to mark an item status as ''on holds shelf''' );
582 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 185, 'MARK_ITEM_ON_ORDER',
583      'Allow a user to mark an item status as ''on order''' );
584 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 186, 'MARK_ITEM_ILL',
585      'Allow a user to mark an item status as ''inter-library loan''' );
586 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 187, 'group_application.user.staff.acq',
587      'Allows a user to add/remove/edit users in the "ACQ" group' );
588 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 188, 'CREATE_PURCHASE_ORDER',
589      'Allows a user to create a purchase order' );
590 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 189, 'VIEW_PURCHASE_ORDER',
591      'Allows a user to view a purchase order' );
592 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 190, 'IMPORT_ACQ_LINEITEM_BIB_RECORD',
593      'Allows a user to import a bib record from the acq staging area (on-order record) into the ILS bib data set' );
594 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 191, 'RECEIVE_PURCHASE_ORDER',
595      'Allows a user to mark a purchase order, lineitem, or individual copy as received' );
596 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 192, 'VIEW_ORG_SETTINGS',
597      'Allows a user to view all org settings at the specified level' );
598 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 193, 'CREATE_MFHD_RECORD',
599      'Allows a user to create a new MFHD record' );
600 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 194, 'UPDATE_MFHD_RECORD',
601      'Allows a user to update an MFHD record' );
602 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 195, 'DELETE_MFHD_RECORD',
603      'Allows a user to delete an MFHD record' );
604 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 196, 'ADMIN_ACQ_FUND',
605      'Allow a user to create/view/update/delete a fund' );
606 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 197, 'group_application.user.staff.acq_admin',
607      'Allows a user to add/remove/edit users in the "Acquisitions Administrators" group' );
608 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 198, 'SET_CIRC_CLAIMS_RETURNED.override',
609      'Allows staff to override the max claims returned value for a patron' );
610 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 199, 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
611      'Allows staff to manually change a patron''s claims returned count' );
612 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 200, 'UPDATE_BILL_NOTE',
613      'Allows staff to edit the note for a bill on a transaction' );
614 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 201, 'UPDATE_PAYMENT_NOTE',
615      'Allows staff to edit the note for a payment on a transaction' );
616 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 202, 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
617      'Allows staff to manually change a patron''s claims never checkout out count' );
618 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 203, 'ADMIN_COPY_LOCATION_ORDER',
619      'Allow a user to create/view/update/delete a copy location order' );
620 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 204, 'ASSIGN_GROUP_PERM',
621      '' );
622 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 205, 'CREATE_AUDIENCE',
623      '' );
624 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 206, 'CREATE_BIB_LEVEL',
625      '' );
626 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 207, 'CREATE_CIRC_DURATION',
627      '' );
628 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 208, 'CREATE_CIRC_MOD',
629      '' );
630 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 209, 'CREATE_COPY_STATUS',
631      '' );
632 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 210, 'CREATE_HOURS_OF_OPERATION',
633      '' );
634 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 211, 'CREATE_ITEM_FORM',
635      '' );
636 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 212, 'CREATE_ITEM_TYPE',
637      '' );
638 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 213, 'CREATE_LANGUAGE',
639      '' );
640 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 214, 'CREATE_LASSO',
641      '' );
642 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 215, 'CREATE_LASSO_MAP',
643      '' );
644 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 216, 'CREATE_LIT_FORM',
645      '' );
646 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 217, 'CREATE_METABIB_FIELD',
647      '' );
648 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 218, 'CREATE_NET_ACCESS_LEVEL',
649      '' );
650 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 219, 'CREATE_ORG_ADDRESS',
651      '' );
652 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 220, 'CREATE_ORG_TYPE',
653      '' );
654 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 221, 'CREATE_ORG_UNIT',
655      '' );
656 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 222, 'CREATE_ORG_UNIT_CLOSING',
657      '' );
658 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 223, 'CREATE_PERM',
659      '' );
660 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 224, 'CREATE_RELEVANCE_ADJUSTMENT',
661      '' );
662 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 225, 'CREATE_SURVEY',
663      '' );
664 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 226, 'CREATE_VR_FORMAT',
665      '' );
666 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 227, 'CREATE_XML_TRANSFORM',
667      '' );
668 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 228, 'DELETE_AUDIENCE',
669      '' );
670 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 229, 'DELETE_BIB_LEVEL',
671      '' );
672 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 230, 'DELETE_CIRC_DURATION',
673      '' );
674 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 231, 'DELETE_CIRC_MOD',
675      '' );
676 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 232, 'DELETE_COPY_STATUS',
677      '' );
678 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 233, 'DELETE_HOURS_OF_OPERATION',
679      '' );
680 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 234, 'DELETE_ITEM_FORM',
681      '' );
682 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 235, 'DELETE_ITEM_TYPE',
683      '' );
684 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 236, 'DELETE_LANGUAGE',
685      '' );
686 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 237, 'DELETE_LASSO',
687      '' );
688 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 238, 'DELETE_LASSO_MAP',
689      '' );
690 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 239, 'DELETE_LIT_FORM',
691      '' );
692 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 240, 'DELETE_METABIB_FIELD',
693      '' );
694 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 241, 'DELETE_NET_ACCESS_LEVEL',
695      '' );
696 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 242, 'DELETE_ORG_ADDRESS',
697      '' );
698 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 243, 'DELETE_ORG_TYPE',
699      '' );
700 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 244, 'DELETE_ORG_UNIT',
701      '' );
702 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 245, 'DELETE_ORG_UNIT_CLOSING',
703      '' );
704 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 246, 'DELETE_PERM',
705      '' );
706 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 247, 'DELETE_RELEVANCE_ADJUSTMENT',
707      '' );
708 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 248, 'DELETE_SURVEY',
709      '' );
710 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 249, 'DELETE_TRANSIT',
711      '' );
712 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 250, 'DELETE_VR_FORMAT',
713      '' );
714 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 251, 'DELETE_XML_TRANSFORM',
715      '' );
716 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 252, 'REMOVE_GROUP_PERM',
717      '' );
718 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 253, 'TRANSIT_COPY',
719      '' );
720 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 254, 'UPDATE_AUDIENCE',
721      '' );
722 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 255, 'UPDATE_BIB_LEVEL',
723      '' );
724 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 256, 'UPDATE_CIRC_DURATION',
725      '' );
726 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 257, 'UPDATE_CIRC_MOD',
727      '' );
728 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 258, 'UPDATE_COPY_NOTE',
729      '' );
730 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 259, 'UPDATE_COPY_STATUS',
731      '' );
732 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 260, 'UPDATE_GROUP_PERM',
733      '' );
734 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 261, 'UPDATE_HOURS_OF_OPERATION',
735      '' );
736 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 262, 'UPDATE_ITEM_FORM',
737      '' );
738 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 263, 'UPDATE_ITEM_TYPE',
739      '' );
740 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 264, 'UPDATE_LANGUAGE',
741      '' );
742 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 265, 'UPDATE_LASSO',
743      '' );
744 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 266, 'UPDATE_LASSO_MAP',
745      '' );
746 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 267, 'UPDATE_LIT_FORM',
747      '' );
748 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 268, 'UPDATE_METABIB_FIELD',
749      '' );
750 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 269, 'UPDATE_NET_ACCESS_LEVEL',
751      '' );
752 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 270, 'UPDATE_ORG_ADDRESS',
753      '' );
754 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 271, 'UPDATE_ORG_TYPE',
755      '' );
756 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 272, 'UPDATE_ORG_UNIT_CLOSING',
757      '' );
758 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 273, 'UPDATE_PERM',
759      '' );
760 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 274, 'UPDATE_RELEVANCE_ADJUSTMENT',
761      '' );
762 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 275, 'UPDATE_SURVEY',
763      '' );
764 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 276, 'UPDATE_TRANSIT',
765      '' );
766 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 277, 'UPDATE_VOLUME_NOTE',
767      '' );
768 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 278, 'UPDATE_VR_FORMAT',
769      '' );
770 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 279, 'UPDATE_XML_TRANSFORM',
771      '' );
772 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 280, 'MERGE_BIB_RECORDS',
773      '' );
774 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 281, 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
775      '' );
776 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 282, 'CREATE_ACQ_FUNDING_SOURCE',
777      '' );
778 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 283, 'CREATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
779      '' );
780 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 284, 'CREATE_AUTHORITY_IMPORT_QUEUE',
781      '' );
782 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 285, 'CREATE_AUTHORITY_RECORD_NOTE',
783      '' );
784 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 286, 'CREATE_BIB_IMPORT_FIELD_DEF',
785      '' );
786 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 287, 'CREATE_BIB_IMPORT_QUEUE',
787      '' );
788 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 288, 'CREATE_LOCALE',
789      '' );
790 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 289, 'CREATE_MARC_CODE',
791      '' );
792 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 290, 'CREATE_TRANSLATION',
793      '' );
794 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 291, 'DELETE_ACQ_FUNDING_SOURCE',
795      '' );
796 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 292, 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
797      '' );
798 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 293, 'DELETE_AUTHORITY_IMPORT_QUEUE',
799      '' );
800 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 294, 'DELETE_AUTHORITY_RECORD_NOTE',
801      '' );
802 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 295, 'DELETE_BIB_IMPORT_IMPORT_FIELD_DEF',
803      '' );
804 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 296, 'DELETE_BIB_IMPORT_QUEUE',
805      '' );
806 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 297, 'DELETE_LOCALE',
807      '' );
808 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 298, 'DELETE_MARC_CODE',
809      '' );
810 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 299, 'DELETE_TRANSLATION',
811      '' );
812 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 300, 'UPDATE_ACQ_FUNDING_SOURCE',
813      '' );
814 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 301, 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
815      '' );
816 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 302, 'UPDATE_AUTHORITY_IMPORT_QUEUE',
817      '' );
818 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 303, 'UPDATE_AUTHORITY_RECORD_NOTE',
819      '' );
820 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 304, 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
821      '' );
822 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 305, 'UPDATE_BIB_IMPORT_QUEUE',
823      '' );
824 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 306, 'UPDATE_LOCALE',
825      '' );
826 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 307, 'UPDATE_MARC_CODE',
827      '' );
828 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 308, 'UPDATE_TRANSLATION',
829      '' );
830 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 309, 'VIEW_ACQ_FUNDING_SOURCE',
831      '' );
832 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 310, 'VIEW_AUTHORITY_RECORD_NOTES',
833      '' );
834 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 311, 'CREATE_IMPORT_ITEM',
835      '' );
836 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 312, 'CREATE_IMPORT_ITEM_ATTR_DEF',
837      '' );
838 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 313, 'CREATE_IMPORT_TRASH_FIELD',
839      '' );
840 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 314, 'DELETE_IMPORT_ITEM',
841      '' );
842 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 315, 'DELETE_IMPORT_ITEM_ATTR_DEF',
843      '' );
844 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 316, 'DELETE_IMPORT_TRASH_FIELD',
845      '' );
846 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 317, 'UPDATE_IMPORT_ITEM',
847      '' );
848 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 318, 'UPDATE_IMPORT_ITEM_ATTR_DEF',
849      '' );
850 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 319, 'UPDATE_IMPORT_TRASH_FIELD',
851      '' );
852 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 320, 'UPDATE_ORG_UNIT_SETTING_ALL',
853      '' );
854 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 321, 'UPDATE_ORG_UNIT_SETTING.circ.lost_materials_processing_fee',
855      '' );
856 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 322, 'UPDATE_ORG_UNIT_SETTING.cat.default_item_price',
857      '' );
858 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 323, 'UPDATE_ORG_UNIT_SETTING.auth.opac_timeout',
859      '' );
860 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 324, 'UPDATE_ORG_UNIT_SETTING.auth.staff_timeout',
861      '' );
862 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 325, 'UPDATE_ORG_UNIT_SETTING.org.bounced_emails',
863      '' );
864 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 326, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_alert_interval',
865      '' );
866 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 327, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_interval',
867      '' );
868 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 328, 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow',
869      '' );
870 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 329, 'UPDATE_ORG_UNIT_SETTING.circ.void_overdue_on_lost',
871      '' );
872 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 330, 'UPDATE_ORG_UNIT_SETTING.circ.hold_stalling.soft',
873      '' );
874 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 331, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.hard',
875      '' );
876 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 332, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.soft',
877      '' );
878 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 333, 'UPDATE_ORG_UNIT_SETTING.opac.barcode_regex',
879      '' );
880 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 334, 'UPDATE_ORG_UNIT_SETTING.global.password_regex',
881      '' );
882 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 335, 'UPDATE_ORG_UNIT_SETTING.circ.item_checkout_history.max',
883      '' );
884 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 336, 'UPDATE_ORG_UNIT_SETTING.circ.reshelving_complete.interval',
885      '' );
886 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 337, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.patron_login_timeout',
887      '' );
888 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 338, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.alert_on_checkout_event',
889      '' );
890 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 339, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.require_patron_password',
891      '' );
892 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 340, 'UPDATE_ORG_UNIT_SETTING.global.juvenile_age_threshold',
893      '' );
894 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 341, 'UPDATE_ORG_UNIT_SETTING.cat.bib.keep_on_empty',
895      '' );
896 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 342, 'UPDATE_ORG_UNIT_SETTING.cat.bib.alert_on_empty',
897      '' );
898 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 343, 'UPDATE_ORG_UNIT_SETTING.patron.password.use_phone',
899      '' );
900 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 344, 'HOLD_ITEM_CHECKED_OUT.override',
901      'Allows a user to place a hold on an item that they already have checked out' );
902 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 345, 'ADMIN_ACQ_CANCEL_CAUSE',
903      'Allow a user to create/update/delete reasons for order cancellations' );
904 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 346, 'ACQ_XFER_MANUAL_DFUND_AMOUNT',
905      'Allow a user to transfer different amounts of money out of one fund and into another' );
906 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 347, 'OVERRIDE_HOLD_HAS_LOCAL_COPY',
907      'Allow a user to override the circ.holds.hold_has_copy_at.block setting' );
908 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 348, 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
909      'Allow a user to change the pickup and transit destination for a captured hold item already in transit' );
910 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 349, 'COPY_NEEDED_FOR_HOLD.override',
911      'Allow a user to force renewal of an item that could fulfill a hold request' );
912 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 350, 'MERGE_AUTH_RECORDS',
913      'Allow a user to merge authority records together' );
914 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 351, 'ALLOW_ALT_TCN',
915      'Allows staff to import a record using an alternate TCN to avoid conflicts' );
916 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 352, 'ADMIN_TRIGGER_EVENT_DEF',
917      'Allow a user to administer trigger event definitions' );
918 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 353, 'ADMIN_TRIGGER_CLEANUP',
919      'Allow a user to create, delete, and update trigger cleanup entries' );
920 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 354, 'CREATE_TRIGGER_CLEANUP',
921      'Allow a user to create trigger cleanup entries' );
922 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 355, 'DELETE_TRIGGER_CLEANUP',
923      'Allow a user to delete trigger cleanup entries' );
924 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 356, 'UPDATE_TRIGGER_CLEANUP',
925      'Allow a user to update trigger cleanup entries' );
926 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 357, 'CREATE_TRIGGER_EVENT_DEF',
927      'Allow a user to create trigger event definitions' );
928 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 358, 'DELETE_TRIGGER_EVENT_DEF',
929      'Allow a user to delete trigger event definitions' );
930 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 359, 'UPDATE_TRIGGER_EVENT_DEF',
931      'Allow a user to update trigger event definitions' );
932 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 360, 'VIEW_TRIGGER_EVENT_DEF',
933      'Allow a user to view trigger event definitions' );
934 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 361, 'ADMIN_TRIGGER_HOOK',
935      'Allow a user to create, update, and delete trigger hooks' );
936 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 362, 'CREATE_TRIGGER_HOOK',
937      'Allow a user to create trigger hooks' );
938 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 363, 'DELETE_TRIGGER_HOOK',
939      'Allow a user to delete trigger hooks' );
940 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 364, 'UPDATE_TRIGGER_HOOK',
941      'Allow a user to update trigger hooks' );
942 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 365, 'ADMIN_TRIGGER_REACTOR',
943      'Allow a user to create, update, and delete trigger reactors' );
944 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 366, 'CREATE_TRIGGER_REACTOR',
945      'Allow a user to create trigger reactors' );
946 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 367, 'DELETE_TRIGGER_REACTOR',
947      'Allow a user to delete trigger reactors' );
948 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 368, 'UPDATE_TRIGGER_REACTOR',
949      'Allow a user to update trigger reactors' );
950 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 369, 'ADMIN_TRIGGER_TEMPLATE_OUTPUT',
951      'Allow a user to delete trigger template output' );
952 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 370, 'DELETE_TRIGGER_TEMPLATE_OUTPUT',
953      'Allow a user to delete trigger template output' );
954 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 371, 'ADMIN_TRIGGER_VALIDATOR',
955      'Allow a user to create, update, and delete trigger validators' );
956 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 372, 'CREATE_TRIGGER_VALIDATOR',
957      'Allow a user to create trigger validators' );
958 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 373, 'DELETE_TRIGGER_VALIDATOR',
959      'Allow a user to delete trigger validators' );
960 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 374, 'UPDATE_TRIGGER_VALIDATOR',
961      'Allow a user to update trigger validators' );
962 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 375, 'HOLD_LOCAL_AVAIL_OVERRIDE',
963      'Allow a user to place a hold despite the availability of a local copy' );
964 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 376, 'ADMIN_BOOKING_RESOURCE',
965      'Enables the user to create/update/delete booking resources' );
966 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 377, 'ADMIN_BOOKING_RESOURCE_TYPE',
967      'Enables the user to create/update/delete booking resource types' );
968 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 378, 'ADMIN_BOOKING_RESOURCE_ATTR',
969      'Enables the user to create/update/delete booking resource attributes' );
970 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 379, 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
971      'Enables the user to create/update/delete booking resource attribute maps' );
972 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 380, 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
973      'Enables the user to create/update/delete booking resource attribute values' );
974 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 381, 'ADMIN_BOOKING_RESERVATION',
975      'Enables the user to create/update/delete booking reservations' );
976 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 382, 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
977      'Enables the user to create/update/delete booking reservation attribute value maps' );
978 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 383, 'RETRIEVE_RESERVATION_PULL_LIST',
979      'Allows a user to retrieve a booking reservation pull list' );
980 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 384, 'CAPTURE_RESERVATION',
981      'Allows a user to capture booking reservations' );
982 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 385, 'UPDATE_RECORD',
983      '' );
984 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 386, 'UPDATE_ORG_UNIT_SETTING.circ.block_renews_for_holds',
985      '' );
986 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 387, 'MERGE_USERS',
987      'Allows user records to be merged' );
988 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 388, 'ISSUANCE_HOLDS',
989      'Allow a user to place holds on serials issuances' );
990 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 389, 'VIEW_CREDIT_CARD_PROCESSING',
991      'View org unit settings related to credit card processing' );
992 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 390, 'ADMIN_CREDIT_CARD_PROCESSING',
993      'Update org unit settings related to credit card processing' );
994 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 391, 'ADMIN_SERIAL_CAPTION_PATTERN',
995         'Create/update/delete serial caption and pattern objects' );
996 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 392, 'ADMIN_SERIAL_SUBSCRIPTION',
997         'Create/update/delete serial subscription objects' );
998 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 393, 'ADMIN_SERIAL_DISTRIBUTION',
999         'Create/update/delete serial distribution objects' );
1000 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 394, 'ADMIN_SERIAL_STREAM',
1001         'Create/update/delete serial stream objects' );
1002 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 395, 'RECEIVE_SERIAL',
1003         'Receive serial items' );
1004
1005 -- Now for the permissions from the IDL.  We don't have descriptions for them.
1006
1007 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 396, 'ADMIN_ACQ_CLAIM' );
1008 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 397, 'ADMIN_ACQ_CLAIM_EVENT_TYPE' );
1009 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 398, 'ADMIN_ACQ_CLAIM_TYPE' );
1010 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 399, 'ADMIN_ACQ_DISTRIB_FORMULA' );
1011 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 400, 'ADMIN_ACQ_FISCAL_YEAR' );
1012 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 401, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT' );
1013 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 402, 'ADMIN_ACQ_FUND_TAG' );
1014 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 403, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT' );
1015 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 404, 'ADMIN_AGE_PROTECT_RULE' );
1016 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 405, 'ADMIN_ASSET_COPY_TEMPLATE' );
1017 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 406, 'ADMIN_BOOKING_RESERVATION_ATTR_MAP' );
1018 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 407, 'ADMIN_CIRC_MATRIX_MATCHPOINT' );
1019 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 408, 'ADMIN_CIRC_MOD' );
1020 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 409, 'ADMIN_CLAIM_POLICY' );
1021 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 410, 'ADMIN_CONFIG_REMOTE_ACCOUNT' );
1022 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 411, 'ADMIN_FIELD_DOC' );
1023 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 412, 'ADMIN_GLOBAL_FLAG' );
1024 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 413, 'ADMIN_GROUP_PENALTY_THRESHOLD' );
1025 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 414, 'ADMIN_HOLD_CANCEL_CAUSE' );
1026 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 415, 'ADMIN_HOLD_MATRIX_MATCHPOINT' );
1027 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 416, 'ADMIN_IDENT_TYPE' );
1028 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 417, 'ADMIN_IMPORT_ITEM_ATTR_DEF' );
1029 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 418, 'ADMIN_INDEX_NORMALIZER' );
1030 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 419, 'ADMIN_INVOICE' );
1031 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 420, 'ADMIN_INVOICE_METHOD' );
1032 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 421, 'ADMIN_INVOICE_PAYMENT_METHOD' );
1033 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 422, 'ADMIN_LINEITEM_MARC_ATTR_DEF' );
1034 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 423, 'ADMIN_MARC_CODE' );
1035 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 424, 'ADMIN_MAX_FINE_RULE' );
1036 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 425, 'ADMIN_MERGE_PROFILE' );
1037 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 426, 'ADMIN_ORG_UNIT_SETTING_TYPE' );
1038 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 427, 'ADMIN_RECURRING_FINE_RULE' );
1039 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 428, 'ADMIN_STANDING_PENALTY' );
1040 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 429, 'ADMIN_SURVEY' );
1041 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 430, 'ADMIN_USER_REQUEST_TYPE' );
1042 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 431, 'ADMIN_USER_SETTING_GROUP' );
1043 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 432, 'ADMIN_USER_SETTING_TYPE' );
1044 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 433, 'ADMIN_Z3950_SOURCE' );
1045 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 434, 'CREATE_BIB_BTYPE' );
1046 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 435, 'CREATE_BIBLIO_FINGERPRINT' );
1047 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 436, 'CREATE_BIB_SOURCE' );
1048 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 437, 'CREATE_BILLING_TYPE' );
1049 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 438, 'CREATE_CN_BTYPE' );
1050 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 439, 'CREATE_COPY_BTYPE' );
1051 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 440, 'CREATE_INVOICE' );
1052 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 441, 'CREATE_INVOICE_ITEM_TYPE' );
1053 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 442, 'CREATE_INVOICE_METHOD' );
1054 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 443, 'CREATE_MERGE_PROFILE' );
1055 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 444, 'CREATE_METABIB_CLASS' );
1056 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 445, 'CREATE_METABIB_SEARCH_ALIAS' );
1057 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 446, 'CREATE_USER_BTYPE' );
1058 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 447, 'DELETE_BIB_BTYPE' );
1059 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 448, 'DELETE_BIBLIO_FINGERPRINT' );
1060 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 449, 'DELETE_BIB_SOURCE' );
1061 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 450, 'DELETE_BILLING_TYPE' );
1062 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 451, 'DELETE_CN_BTYPE' );
1063 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 452, 'DELETE_COPY_BTYPE' );
1064 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 453, 'DELETE_INVOICE_ITEM_TYPE' );
1065 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 454, 'DELETE_INVOICE_METHOD' );
1066 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 455, 'DELETE_MERGE_PROFILE' );
1067 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 456, 'DELETE_METABIB_CLASS' );
1068 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 457, 'DELETE_METABIB_SEARCH_ALIAS' );
1069 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 458, 'DELETE_USER_BTYPE' );
1070 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 459, 'MANAGE_CLAIM' );
1071 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 460, 'UPDATE_BIB_BTYPE' );
1072 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 461, 'UPDATE_BIBLIO_FINGERPRINT' );
1073 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 462, 'UPDATE_BIB_SOURCE' );
1074 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 463, 'UPDATE_BILLING_TYPE' );
1075 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 464, 'UPDATE_CN_BTYPE' );
1076 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 465, 'UPDATE_COPY_BTYPE' );
1077 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 466, 'UPDATE_INVOICE_ITEM_TYPE' );
1078 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 467, 'UPDATE_INVOICE_METHOD' );
1079 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 468, 'UPDATE_MERGE_PROFILE' );
1080 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 469, 'UPDATE_METABIB_CLASS' );
1081 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 470, 'UPDATE_METABIB_SEARCH_ALIAS' );
1082 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 471, 'UPDATE_USER_BTYPE' );
1083 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 472, 'user_request.create' );
1084 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 473, 'user_request.delete' );
1085 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 474, 'user_request.update' );
1086 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 475, 'user_request.view' );
1087 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 476, 'VIEW_ACQ_FUND_ALLOCATION_PERCENT' );
1088 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 477, 'VIEW_CIRC_MATRIX_MATCHPOINT' );
1089 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 478, 'VIEW_CLAIM' );
1090 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 479, 'VIEW_GROUP_PENALTY_THRESHOLD' );
1091 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 480, 'VIEW_HOLD_MATRIX_MATCHPOINT' );
1092 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 481, 'VIEW_INVOICE' );
1093 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 482, 'VIEW_MERGE_PROFILE' );
1094 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 483, 'VIEW_SERIAL_SUBSCRIPTION' );
1095 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 484, 'VIEW_STANDING_PENALTY' );
1096
1097 -- For every permission in the temp_perm table that has a matching
1098 -- permission in the real table: record the original id.
1099
1100 UPDATE permission.temp_perm AS tp
1101 SET old_id =
1102         (
1103                 SELECT id
1104                 FROM permission.perm_list AS ppl
1105                 WHERE ppl.code = tp.code
1106         )
1107 WHERE code IN ( SELECT code FROM permission.perm_list );
1108
1109 -- Start juggling ids.
1110
1111 -- If any permissions have negative ids (with the special exception of -1),
1112 -- we need to move them into the positive range in order to avoid duplicate
1113 -- key problems (since we are going to use the negative range as a temporary
1114 -- staging area).
1115
1116 -- First, move any predefined permissions that have negative ids (again with
1117 -- the special exception of -1).  Temporarily give them positive ids based on
1118 -- the sequence.
1119
1120 UPDATE permission.perm_list
1121 SET id = NEXTVAL('permission.perm_list_id_seq'::regclass)
1122 WHERE id < -1
1123   AND code IN (SELECT code FROM permission.temp_perm);
1124
1125 -- Identify any non-predefined permissions whose ids are either negative
1126 -- or within the range (0-1000) reserved for predefined permissions.
1127 -- Assign them ids above 1000, based on the sequence.  Record the new
1128 -- ids in the temp_perm table.
1129
1130 INSERT INTO permission.temp_perm ( id, code, description, old_id, predefined )
1131 (
1132         SELECT NEXTVAL('permission.perm_list_id_seq'::regclass),
1133                 code, description, id, false
1134         FROM permission.perm_list
1135         WHERE  ( id < -1 OR id BETWEEN 0 AND 1000 )
1136         AND code NOT IN (SELECT code FROM permission.temp_perm)
1137 );
1138
1139 -- Now update the ids of those non-predefined permissions, using the
1140 -- values assigned in the previous step.
1141
1142 UPDATE permission.perm_list AS ppl
1143 SET id = (
1144                 SELECT id
1145                 FROM permission.temp_perm AS tp
1146                 WHERE tp.code = ppl.code
1147         )
1148 WHERE id IN ( SELECT old_id FROM permission.temp_perm WHERE NOT predefined );
1149
1150 -- Now the negative ids have been eliminated, except for -1.  Move all the
1151 -- predefined permissions temporarily into the negative range.
1152
1153 UPDATE permission.perm_list
1154 SET id = -1 - id
1155 WHERE id <> -1
1156 AND code IN ( SELECT code from permission.temp_perm WHERE predefined );
1157
1158 -- Apply the final ids to the existing predefined permissions.
1159
1160 UPDATE permission.perm_list AS ppl
1161 SET id =
1162         (
1163                 SELECT id
1164                 FROM permission.temp_perm AS tp
1165                 WHERE tp.code = ppl.code
1166         )
1167 WHERE
1168         id <> -1
1169         AND ppl.code IN
1170         (
1171                 SELECT code from permission.temp_perm
1172                 WHERE predefined
1173                 AND old_id IS NOT NULL
1174         );
1175
1176 -- If there are any predefined permissions that don't exist yet in
1177 -- permission.perm_list, insert them now.
1178
1179 INSERT INTO permission.perm_list ( id, code, description )
1180 (
1181         SELECT id, code, description
1182         FROM permission.temp_perm
1183         WHERE old_id IS NULL
1184 );
1185
1186 -- Reset the sequence to the lowest feasible value.  This may or may not
1187 -- accomplish anything, but it will do no harm.
1188
1189 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, GREATEST( 
1190         (SELECT MAX(id) FROM permission.perm_list), 1000 ));
1191
1192 -- If any permission lacks a description, use the code as a description.
1193 -- It's better than nothing.
1194
1195 UPDATE permission.perm_list
1196 SET description = code
1197 WHERE description IS NULL
1198    OR description = '';
1199
1200 -- Thus endeth the Great Renumbering.
1201
1202 -- Having massaged the permissions, massage the way they are assigned, by inserting
1203 -- rows into permission.grp_perm_map.  Some of these permissions may have already
1204 -- been assigned, so we insert the rows only if they aren't already there.
1205
1206 -- for backwards compat, give everyone the permission
1207 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1208     SELECT 1, id, 0, false FROM permission.perm_list AS perm
1209         WHERE code = 'HOLD_ITEM_CHECKED_OUT.override'
1210                 AND NOT EXISTS (
1211                         SELECT 1
1212                         FROM permission.grp_perm_map AS map
1213                         WHERE
1214                                 grp = 1
1215                                 AND map.perm = perm.id
1216                 );
1217
1218 -- Add trigger administration permissions to the Local System Administrator group.
1219 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1220     SELECT 10, id, 1, false FROM permission.perm_list AS perm
1221     WHERE (
1222                 perm.code LIKE 'ADMIN_TRIGGER%'
1223         OR perm.code LIKE 'CREATE_TRIGGER%'
1224         OR perm.code LIKE 'DELETE_TRIGGER%'
1225         OR perm.code LIKE 'UPDATE_TRIGGER%'
1226         ) AND NOT EXISTS (
1227                 SELECT 1
1228                 FROM permission.grp_perm_map AS map
1229                 WHERE
1230                         grp = 10
1231                         AND map.perm = perm.id
1232         );
1233
1234 -- View trigger permissions are required at a consortial level for initial setup
1235 -- (as before, only if the row doesn't already exist)
1236 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1237     SELECT 10, id, 0, false FROM permission.perm_list AS perm
1238         WHERE code LIKE 'VIEW_TRIGGER%'
1239                 AND NOT EXISTS (
1240                         SELECT 1
1241                         FROM permission.grp_perm_map AS map
1242                         WHERE
1243                                 grp = 10
1244                                 AND map.perm = perm.id
1245                 );
1246
1247 -- Permission for merging auth records may already be defined,
1248 -- so add it only if it isn't there.
1249 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1250     SELECT 4, id, 1, false FROM permission.perm_list AS perm
1251         WHERE code = 'MERGE_AUTH_RECORDS'
1252                 AND NOT EXISTS (
1253                         SELECT 1
1254                         FROM permission.grp_perm_map AS map
1255                         WHERE
1256                                 grp = 4
1257                                 AND map.perm = perm.id
1258                 );
1259
1260 -- Create a reference table as parent to both
1261 -- config.org_unit_setting_type and config_usr_setting_type
1262
1263 CREATE TABLE config.settings_group (
1264     name    TEXT PRIMARY KEY,
1265     label   TEXT UNIQUE NOT NULL -- I18N
1266 );
1267
1268 -- org_unit setting types
1269 CREATE TABLE config.org_unit_setting_type (
1270     name            TEXT    PRIMARY KEY,
1271     label           TEXT    UNIQUE NOT NULL,
1272     grp             TEXT    REFERENCES config.settings_group (name),
1273     description     TEXT,
1274     datatype        TEXT    NOT NULL DEFAULT 'string',
1275     fm_class        TEXT,
1276     view_perm       INT,
1277     update_perm     INT,
1278     --
1279     -- define valid datatypes
1280     --
1281     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1282     ( 'bool', 'integer', 'float', 'currency', 'interval',
1283       'date', 'string', 'object', 'array', 'link' ) ),
1284     --
1285     -- fm_class is meaningful only for 'link' datatype
1286     --
1287     CONSTRAINT coust_no_empty_link CHECK
1288     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
1289       ( datatype <> 'link' AND fm_class IS NULL ) ),
1290         CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id)
1291                 ON UPDATE CASCADE
1292                 ON DELETE RESTRICT
1293                 DEFERRABLE INITIALLY DEFERRED,
1294         CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id)
1295                 ON UPDATE CASCADE
1296                 DEFERRABLE INITIALLY DEFERRED
1297 );
1298
1299 CREATE TABLE config.usr_setting_type (
1300
1301     name TEXT PRIMARY KEY,
1302     opac_visible BOOL NOT NULL DEFAULT FALSE,
1303     label TEXT UNIQUE NOT NULL,
1304     description TEXT,
1305     grp             TEXT    REFERENCES config.settings_group (name),
1306     datatype TEXT NOT NULL DEFAULT 'string',
1307     fm_class TEXT,
1308
1309     --
1310     -- define valid datatypes
1311     --
1312     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1313     ( 'bool', 'integer', 'float', 'currency', 'interval',
1314         'date', 'string', 'object', 'array', 'link' ) ),
1315
1316     --
1317     -- fm_class is meaningful only for 'link' datatype
1318     --
1319     CONSTRAINT coust_no_empty_link CHECK
1320     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
1321         ( datatype <> 'link' AND fm_class IS NULL ) )
1322
1323 );
1324
1325 --------------------------------------
1326 -- Seed data for org_unit_setting_type
1327 --------------------------------------
1328
1329 INSERT into config.org_unit_setting_type
1330 ( name, label, description, datatype ) VALUES
1331
1332 ( 'auth.opac_timeout',
1333   'OPAC Inactivity Timeout (in seconds)',
1334   null,
1335   'integer' ),
1336
1337 ( 'auth.staff_timeout',
1338   'Staff Login Inactivity Timeout (in seconds)',
1339   null,
1340   'integer' ),
1341
1342 ( 'circ.lost_materials_processing_fee',
1343   'Lost Materials Processing Fee',
1344   null,
1345   'currency' ),
1346
1347 ( 'cat.default_item_price',
1348   'Default Item Price',
1349   null,
1350   'currency' ),
1351
1352 ( 'org.bounced_emails',
1353   'Sending email address for patron notices',
1354   null,
1355   'string' ),
1356
1357 ( 'circ.hold_expire_alert_interval',
1358   'Holds: Expire Alert Interval',
1359   'Amount of time before a hold expires at which point the patron should be alerted',
1360   'interval' ),
1361
1362 ( 'circ.hold_expire_interval',
1363   'Holds: Expire Interval',
1364   'Amount of time after a hold is placed before the hold expires.  Example "100 days"',
1365   'interval' ),
1366
1367 ( 'credit.payments.allow',
1368   'Allow Credit Card Payments',
1369   'If enabled, patrons will be able to pay fines accrued at this location via credit card',
1370   'bool' ),
1371
1372 ( 'global.default_locale',
1373   'Global Default Locale',
1374   null,
1375   'string' ),
1376
1377 ( 'circ.void_overdue_on_lost',
1378   'Void overdue fines when items are marked lost',
1379   null,
1380   'bool' ),
1381
1382 ( 'circ.hold_stalling.soft',
1383   'Holds: Soft stalling interval',
1384   'How long to wait before allowing remote items to be opportunistically captured for a hold.  Example "5 days"',
1385   'interval' ),
1386
1387 ( 'circ.hold_stalling_hard',
1388   'Holds: Hard stalling interval',
1389   '',
1390   'interval' ),
1391
1392 ( 'circ.hold_boundary.hard',
1393   'Holds: Hard boundary',
1394   null,
1395   'integer' ),
1396
1397 ( 'circ.hold_boundary.soft',
1398   'Holds: Soft boundary',
1399   null,
1400   'integer' ),
1401
1402 ( 'opac.barcode_regex',
1403   'Patron barcode format',
1404   'Regular expression defining the patron barcode format',
1405   'string' ),
1406
1407 ( 'global.password_regex',
1408   'Password format',
1409   'Regular expression defining the password format',
1410   'string' ),
1411
1412 ( 'circ.item_checkout_history.max',
1413   'Maximum previous checkouts displayed',
1414   'This is the maximum number of previous circulations the staff client will display when investigating item details',
1415   'integer' ),
1416
1417 ( 'circ.reshelving_complete.interval',
1418   'Change reshelving status interval',
1419   'Amount of time to wait before changing an item from "reshelving" status to "available".  Examples: "1 day", "6 hours"',
1420   'interval' ),
1421
1422 ( 'circ.holds.default_estimated_wait_interval',
1423   'Holds: Default Estimated Wait',
1424   '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.',
1425   'interval' ),
1426
1427 ( 'circ.holds.min_estimated_wait_interval',
1428   'Holds: Minimum Estimated Wait',
1429   '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.',
1430   'interval' ),
1431
1432 ( 'circ.selfcheck.patron_login_timeout',
1433   'Selfcheck: Patron Login Timeout (in seconds)',
1434   'Number of seconds of inactivity before the patron is logged out of the selfcheck interface',
1435   'integer' ),
1436
1437 ( 'circ.selfcheck.alert.popup',
1438   'Selfcheck: Pop-up alert for errors',
1439   'If true, checkout/renewal errors will cause a pop-up window in addition to the on-screen message',
1440   'bool' ),
1441
1442 ( 'circ.selfcheck.require_patron_password',
1443   'Selfcheck: Require patron password',
1444   'If true, patrons will be required to enter their password in addition to their username/barcode to log into the selfcheck interface',
1445   'bool' ),
1446
1447 ( 'global.juvenile_age_threshold',
1448   'Juvenile Age Threshold',
1449   'The age at which a user is no long considered a juvenile.  For example, "18 years".',
1450   'interval' ),
1451
1452 ( 'cat.bib.keep_on_empty',
1453   'Retain empty bib records',
1454   'Retain a bib record even when all attached copies are deleted',
1455   'bool' ),
1456
1457 ( 'cat.bib.alert_on_empty',
1458   'Alert on empty bib records',
1459   'Alert staff when the last copy for a record is being deleted',
1460   'bool' ),
1461
1462 ( 'patron.password.use_phone',
1463   'Patron: password from phone #',
1464   'Use the last 4 digits of the patrons phone number as the default password when creating new users',
1465   'bool' ),
1466
1467 ( 'circ.charge_on_damaged',
1468   'Charge item price when marked damaged',
1469   'Charge item price when marked damaged',
1470   'bool' ),
1471
1472 ( 'circ.charge_lost_on_zero',
1473   'Charge lost on zero',
1474   '',
1475   'bool' ),
1476
1477 ( 'circ.damaged_item_processing_fee',
1478   'Charge processing fee for damaged items',
1479   'Charge processing fee for damaged items',
1480   'currency' ),
1481
1482 ( 'circ.void_lost_on_checkin',
1483   'Circ: Void lost item billing when returned',
1484   'Void lost item billing when returned',
1485   'bool' ),
1486
1487 ( 'circ.max_accept_return_of_lost',
1488   'Circ: Void lost max interval',
1489   'Items that have been lost this long will not result in voided billings when returned.  E.g. ''6 months''',
1490   'interval' ),
1491
1492 ( 'circ.void_lost_proc_fee_on_checkin',
1493   'Circ: Void processing fee on lost item return',
1494   'Void processing fee when lost item returned',
1495   'bool' ),
1496
1497 ( 'circ.restore_overdue_on_lost_return',
1498   'Circ: Restore overdues on lost item return',
1499   'Restore overdue fines on lost item return',
1500   'bool' ),
1501
1502 ( 'circ.lost_immediately_available',
1503   'Circ: Lost items usable on checkin',
1504   'Lost items are usable on checkin instead of going ''home'' first',
1505   'bool' ),
1506
1507 ( 'circ.holds_fifo',
1508   'Holds: FIFO',
1509   'Force holds to a more strict First-In, First-Out capture',
1510   'bool' ),
1511
1512 ( 'opac.allow_pending_address',
1513   'OPAC: Allow pending addresses',
1514   'If enabled, patrons can create and edit existing addresses.  Addresses are kept in a pending state until staff approves the changes',
1515   'bool' ),
1516
1517 ( 'ui.circ.show_billing_tab_on_bills',
1518   'Show billing tab first when bills are present',
1519   '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',
1520   'bool' ),
1521
1522 ( 'ui.general.idle_timeout',
1523     'GUI: Idle timeout',
1524     '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).',
1525     'integer' ),
1526
1527 ( 'ui.circ.in_house_use.entry_cap',
1528   'GUI: Record In-House Use: Maximum # of uses allowed per entry.',
1529   'The # of uses entry in the Record In-House Use interface may not exceed the value of this setting.',
1530   'integer' ),
1531
1532 ( 'ui.circ.in_house_use.entry_warn',
1533   'GUI: Record In-House Use: # of uses threshold for Are You Sure? dialog.',
1534   'In the Record In-House Use interface, a submission attempt will warn if the # of uses field exceeds the value of this setting.',
1535   'integer' ),
1536
1537 ( 'acq.default_circ_modifier',
1538   'Default circulation modifier',
1539   null,
1540   'string' ),
1541
1542 ( 'acq.tmp_barcode_prefix',
1543   'Temporary barcode prefix',
1544   null,
1545   'string' ),
1546
1547 ( 'acq.tmp_callnumber_prefix',
1548   'Temporary call number prefix',
1549   null,
1550   'string' ),
1551
1552 ( 'ui.circ.patron_summary.horizontal',
1553   'Patron circulation summary is horizontal',
1554   null,
1555   'bool' ),
1556
1557 ( 'ui.staff.require_initials',
1558   oils_i18n_gettext('ui.staff.require_initials', 'GUI: Require staff initials for entry/edit of item/patron/penalty notes/messages.', 'coust', 'label'),
1559   oils_i18n_gettext('ui.staff.require_initials', 'Appends staff initials and edit date into note content.', 'coust', 'description'),
1560   'bool' ),
1561
1562 ( 'ui.general.button_bar',
1563   'Button bar',
1564   null,
1565   'bool' ),
1566
1567 ( 'circ.hold_shelf_status_delay',
1568   'Hold Shelf Status Delay',
1569   '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.',
1570   'interval' ),
1571
1572 ( 'circ.patron_invalid_address_apply_penalty',
1573   'Invalid patron address penalty',
1574   'When set, if a patron address is set to invalid, a penalty is applied.',
1575   'bool' ),
1576
1577 ( 'circ.checkout_fills_related_hold',
1578   'Checkout Fills Related Hold',
1579   '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',
1580   'bool'),
1581
1582 ( 'circ.selfcheck.auto_override_checkout_events',
1583   'Selfcheck override events list',
1584   'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction',
1585   'array' ),
1586
1587 ( 'circ.staff_client.do_not_auto_attempt_print',
1588   'Disable Automatic Print Attempt Type List',
1589   '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).',
1590   'array' ),
1591
1592 ( 'ui.patron.default_inet_access_level',
1593   'Default level of patrons'' internet access',
1594   null,
1595   'integer' ),
1596
1597 ( 'circ.max_patron_claim_return_count',
1598     'Max Patron Claims Returned Count',
1599     'When this count is exceeded, a staff override is required to mark the item as claims returned',
1600     'integer' ),
1601
1602 ( 'circ.obscure_dob',
1603     'Obscure the Date of Birth field',
1604     '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.',
1605     'bool' ),
1606
1607 ( 'circ.auto_hide_patron_summary',
1608     'GUI: Toggle off the patron summary sidebar after first view.',
1609     'When true, the patron summary sidebar will collapse after a new patron sub-interface is selected.',
1610     'bool' ),
1611
1612 ( 'credit.processor.default',
1613     'Credit card processing: Name default credit processor',
1614     'This can be "AuthorizeNet", "PayPal" (for the Website Payment Pro API), or "PayflowPro".',
1615     'string' ),
1616
1617 ( 'credit.processor.authorizenet.enabled',
1618     'Credit card processing: AuthorizeNet enabled',
1619     '',
1620     'bool' ),
1621
1622 ( 'credit.processor.authorizenet.login',
1623     'Credit card processing: AuthorizeNet login',
1624     '',
1625     'string' ),
1626
1627 ( 'credit.processor.authorizenet.password',
1628     'Credit card processing: AuthorizeNet password',
1629     '',
1630     'string' ),
1631
1632 ( 'credit.processor.authorizenet.server',
1633     'Credit card processing: AuthorizeNet server',
1634     'Required if using a developer/test account with AuthorizeNet',
1635     'string' ),
1636
1637 ( 'credit.processor.authorizenet.testmode',
1638     'Credit card processing: AuthorizeNet test mode',
1639     '',
1640     'bool' ),
1641
1642 ( 'credit.processor.paypal.enabled',
1643     'Credit card processing: PayPal enabled',
1644     '',
1645     'bool' ),
1646 ( 'credit.processor.paypal.login',
1647     'Credit card processing: PayPal login',
1648     '',
1649     'string' ),
1650 ( 'credit.processor.paypal.password',
1651     'Credit card processing: PayPal password',
1652     '',
1653     'string' ),
1654 ( 'credit.processor.paypal.signature',
1655     'Credit card processing: PayPal signature',
1656     '',
1657     'string' ),
1658 ( 'credit.processor.paypal.testmode',
1659     'Credit card processing: PayPal test mode',
1660     '',
1661     'bool' ),
1662
1663 ( 'ui.admin.work_log.max_entries',
1664     oils_i18n_gettext('ui.admin.work_log.max_entries', 'GUI: Work Log: Maximum Actions Logged', 'coust', 'label'),
1665     oils_i18n_gettext('ui.admin.work_log.max_entries', 'Maximum entries for "Most Recent Staff Actions" section of the Work Log interface.', 'coust', 'description'),
1666   'interval' ),
1667
1668 ( 'ui.admin.patron_log.max_entries',
1669     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'GUI: Work Log: Maximum Patrons Logged', 'coust', 'label'),
1670     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'Maximum entries for "Most Recently Affected Patrons..." section of the Work Log interface.', 'coust', 'description'),
1671   'interval' ),
1672
1673 ( 'lib.courier_code',
1674     oils_i18n_gettext('lib.courier_code', 'Courier Code', 'coust', 'label'),
1675     oils_i18n_gettext('lib.courier_code', 'Courier Code for the library.  Available in transit slip templates as the %courier_code% macro.', 'coust', 'description'),
1676     'string'),
1677
1678 ( 'circ.block_renews_for_holds',
1679     oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
1680     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'),
1681     'bool' ),
1682
1683 ( 'circ.password_reset_request_per_user_limit',
1684     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
1685     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'),
1686     'string'),
1687
1688 ( 'circ.password_reset_request_time_to_live',
1689     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
1690     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'),
1691     'string'),
1692
1693 ( 'circ.password_reset_request_throttle',
1694     oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
1695     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'),
1696     'string')
1697 ;
1698
1699 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1700         'ui.circ.suppress_checkin_popups',
1701         oils_i18n_gettext(
1702             'ui.circ.suppress_checkin_popups', 
1703             'Circ: Suppress popup-dialogs during check-in.', 
1704             'coust', 
1705             'label'),
1706         oils_i18n_gettext(
1707             'ui.circ.suppress_checkin_popups', 
1708             'Circ: Suppress popup-dialogs during check-in.', 
1709             'coust', 
1710             'description'),
1711         'bool'
1712 );
1713
1714 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1715         'format.date',
1716         oils_i18n_gettext(
1717             'format.date',
1718             'GUI: Format Dates with this pattern.', 
1719             'coust', 
1720             'label'),
1721         oils_i18n_gettext(
1722             'format.date',
1723             'GUI: Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010")', 
1724             'coust', 
1725             'description'),
1726         'string'
1727 ), (
1728         'format.time',
1729         oils_i18n_gettext(
1730             'format.time',
1731             'GUI: Format Times with this pattern.', 
1732             'coust', 
1733             'label'),
1734         oils_i18n_gettext(
1735             'format.time',
1736             '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")', 
1737             'coust', 
1738             'description'),
1739         'string'
1740 );
1741
1742 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1743         'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1744         oils_i18n_gettext(
1745             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1746             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1747             'coust', 
1748             'label'),
1749         oils_i18n_gettext(
1750             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1751             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1752             'coust', 
1753             'description'),
1754         'bool'
1755 );
1756
1757 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1758         'url.remote_column_settings',
1759         oils_i18n_gettext(
1760             'url.remote_column_settings',
1761             'GUI: URL for remote directory containing list column settings.', 
1762             'coust', 
1763             'label'),
1764         oils_i18n_gettext(
1765             'url.remote_column_settings',
1766             '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.', 
1767             'coust', 
1768             'description'),
1769         'string'
1770 );
1771
1772 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1773         'gui.disable_local_save_columns',
1774         oils_i18n_gettext(
1775             'gui.disable_local_save_columns',
1776             'GUI: Disable the ability to save list column configurations locally.', 
1777             'coust', 
1778             'label'),
1779         oils_i18n_gettext(
1780             'gui.disable_local_save_columns',
1781             '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.', 
1782             'coust', 
1783             'description'),
1784         'bool'
1785 );
1786
1787 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1788         'circ.password_reset_request_requires_matching_email',
1789         oils_i18n_gettext(
1790             'circ.password_reset_request_requires_matching_email',
1791             'Circulation: Require matching email address for password reset requests', 
1792             'coust', 
1793             'label'),
1794         oils_i18n_gettext(
1795             'circ.password_reset_request_requires_matching_email',
1796             'Circulation: Require matching email address for password reset requests', 
1797             'coust', 
1798             'description'),
1799         'bool'
1800 );
1801
1802 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1803         'circ.holds.expired_patron_block',
1804         oils_i18n_gettext(
1805             'circ.holds.expired_patron_block',
1806             'Circulation: Block hold request if hold recipient privileges have expired', 
1807             'coust', 
1808             'label'),
1809         oils_i18n_gettext(
1810             'circ.holds.expired_patron_block',
1811             'Circulation: Block hold request if hold recipient privileges have expired', 
1812             'coust', 
1813             'description'),
1814         'bool'
1815 );
1816
1817 INSERT INTO config.org_unit_setting_type
1818     (name, label, description, datatype) VALUES (
1819         'circ.booking_reservation.default_elbow_room',
1820         oils_i18n_gettext(
1821             'circ.booking_reservation.default_elbow_room',
1822             'Booking: Elbow room',
1823             'coust',
1824             'label'
1825         ),
1826         oils_i18n_gettext(
1827             'circ.booking_reservation.default_elbow_room',
1828             '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.',
1829             'coust',
1830             'label'
1831         ),
1832         'interval'
1833     );
1834
1835 -- Org_unit_setting_type(s) that need an fm_class:
1836 INSERT into config.org_unit_setting_type
1837 ( name, label, description, datatype, fm_class ) VALUES
1838 ( 'acq.default_copy_location',
1839   'Default copy location',
1840   null,
1841   'link',
1842   'acpl' );
1843
1844 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1845     'circ.holds.org_unit_target_weight',
1846     'Holds: Org Unit Target Weight',
1847     '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.',
1848     'integer'
1849 );
1850
1851 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1852     'circ.holds.target_holds_by_org_unit_weight',
1853     'Holds: Use weight-based hold targeting',
1854     'Use library weight based hold targeting',
1855     'bool'
1856 );
1857
1858 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1859     'circ.holds.max_org_unit_target_loops',
1860     'Holds: Maximum library target attempts',
1861     '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',
1862     'integer'
1863 );
1864
1865
1866 -- Org setting for overriding the circ lib of a precat copy
1867 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1868     'circ.pre_cat_copy_circ_lib',
1869     'Pre-cat Item Circ Lib',
1870     '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',
1871     'string'
1872 );
1873
1874 -- Circ auto-renew interval setting
1875 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1876     'circ.checkout_auto_renew_age',
1877     'Checkout auto renew age',
1878     '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',
1879     'interval'
1880 );
1881
1882 -- Setting for behind the desk hold pickups
1883 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1884     'circ.holds.behind_desk_pickup_supported',
1885     'Holds: Behind Desk Pickup Supported',
1886     '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',
1887     'bool'
1888 );
1889
1890 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1891         'acq.holds.allow_holds_from_purchase_request',
1892         oils_i18n_gettext(
1893             'acq.holds.allow_holds_from_purchase_request', 
1894             'Allows patrons to create automatic holds from purchase requests.', 
1895             'coust', 
1896             'label'),
1897         oils_i18n_gettext(
1898             'acq.holds.allow_holds_from_purchase_request', 
1899             'Allows patrons to create automatic holds from purchase requests.', 
1900             'coust', 
1901             'description'),
1902         'bool'
1903 );
1904
1905 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1906     'circ.holds.target_skip_me',
1907     'Skip For Hold Targeting',
1908     'When true, don''t target any copies at this org unit for holds',
1909     'bool'
1910 );
1911
1912 -- claims returned mark item missing 
1913 INSERT INTO
1914     config.org_unit_setting_type ( name, label, description, datatype )
1915     VALUES (
1916         'circ.claim_return.mark_missing',
1917         'Claim Return: Mark copy as missing', 
1918         'When a circ is marked as claims-returned, also mark the copy as missing',
1919         'bool'
1920     );
1921
1922 -- claims never checked out mark item missing 
1923 INSERT INTO
1924     config.org_unit_setting_type ( name, label, description, datatype )
1925     VALUES (
1926         'circ.claim_never_checked_out.mark_missing',
1927         'Claim Never Checked Out: Mark copy as missing', 
1928         'When a circ is marked as claims-never-checked-out, mark the copy as missing',
1929         'bool'
1930     );
1931
1932 -- mark damaged void overdue setting
1933 INSERT INTO
1934     config.org_unit_setting_type ( name, label, description, datatype )
1935     VALUES (
1936         'circ.damaged.void_ovedue',
1937         'Mark item damaged voids overdues',
1938         'When an item is marked damaged, overdue fines on the most recent circulation are voided.',
1939         'bool'
1940     );
1941
1942 -- hold cancel display limits
1943 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1944     VALUES (
1945         'circ.holds.canceled.display_count',
1946         'Holds: Canceled holds display count',
1947         'How many canceled holds to show in patron holds interfaces',
1948         'integer'
1949     );
1950
1951 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1952     VALUES (
1953         'circ.holds.canceled.display_age',
1954         'Holds: Canceled holds display age',
1955         'Show all canceled holds that were canceled within this amount of time',
1956         'interval'
1957     );
1958
1959 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1960     VALUES (
1961         'circ.holds.uncancel.reset_request_time',
1962         'Holds: Reset request time on un-cancel',
1963         'When a hold is uncanceled, reset the request time to push it to the end of the queue',
1964         'bool'
1965     );
1966
1967 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1968     VALUES (
1969         'circ.holds.default_shelf_expire_interval',
1970         'Default hold shelf expire interval',
1971         '',
1972         'interval'
1973 );
1974
1975 INSERT INTO config.org_unit_setting_type (name, label, description, datatype, fm_class)
1976     VALUES (
1977         'circ.claim_return.copy_status', 
1978         'Claim Return Copy Status', 
1979         'Claims returned copies are put into this status.  Default is to leave the copy in the Checked Out status',
1980         'link', 
1981         'ccs' 
1982     );
1983
1984 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
1985     VALUES ( 
1986         'circ.max_fine.cap_at_price',
1987         oils_i18n_gettext('circ.max_fine.cap_at_price', 'Circ: Cap Max Fine at Item Price', 'coust', 'label'),
1988         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'),
1989         'bool' 
1990     );
1991
1992 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) 
1993     VALUES ( 
1994         'circ.holds.clear_shelf.copy_status',
1995         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Holds: Clear shelf copy status', 'coust', 'label'),
1996         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'),
1997         'link',
1998         'ccs'
1999     );
2000
2001 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2002     VALUES ( 
2003         'circ.selfcheck.workstation_required',
2004         oils_i18n_gettext('circ.selfcheck.workstation_required', 'Selfcheck: Workstation Required', 'coust', 'label'),
2005         oils_i18n_gettext('circ.selfcheck.workstation_required', 'All selfcheck stations must use a workstation', 'coust', 'description'),
2006         'bool'
2007     ), (
2008         'circ.selfcheck.patron_password_required',
2009         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Selfcheck: Require Patron Password', 'coust', 'label'),
2010         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Patron must log in with barcode and password at selfcheck station', 'coust', 'description'),
2011         'bool'
2012     );
2013
2014 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2015     VALUES ( 
2016         'circ.selfcheck.alert.sound',
2017         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Selfcheck: Audio Alerts', 'coust', 'label'),
2018         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Use audio alerts for selfcheck events', 'coust', 'description'),
2019         'bool'
2020     );
2021
2022 INSERT INTO
2023     config.org_unit_setting_type (name, label, description, datatype)
2024     VALUES (
2025         'notice.telephony.callfile_lines',
2026         'Telephony: Arbitrary line(s) to include in each notice callfile',
2027         $$
2028         This overrides lines from opensrf.xml.
2029         Line(s) must be valid for your target server and platform
2030         (e.g. Asterisk 1.4).
2031         $$,
2032         'string'
2033     );
2034
2035 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2036     VALUES ( 
2037         'circ.offline.username_allowed',
2038         oils_i18n_gettext('circ.offline.username_allowed', 'Offline: Patron Usernames Allowed', 'coust', 'label'),
2039         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'),
2040         'bool'
2041     );
2042
2043 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2044 VALUES (
2045     'acq.fund.balance_limit.warn',
2046     oils_i18n_gettext('acq.fund.balance_limit.warn', 'Fund Spending Limit for Warning', 'coust', 'label'),
2047     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'),
2048     'integer'
2049 );
2050
2051 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2052 VALUES (
2053     'acq.fund.balance_limit.block',
2054     oils_i18n_gettext('acq.fund.balance_limit.block', 'Fund Spending Limit for Block', 'coust', 'label'),
2055     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'),
2056     'integer'
2057 );
2058
2059 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2060     VALUES (
2061         'circ.holds.hold_has_copy_at.alert',
2062         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'Holds: Has Local Copy Alert', 'coust', 'label'),
2063         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'),
2064         'bool'
2065     ),(
2066         'circ.holds.hold_has_copy_at.block',
2067         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'Holds: Has Local Copy Block', 'coust', 'label'),
2068         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'),
2069         'bool'
2070     );
2071
2072 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2073 VALUES (
2074     'auth.persistent_login_interval',
2075     oils_i18n_gettext('auth.persistent_login_interval', 'Persistent Login Duration', 'coust', 'label'),
2076     oils_i18n_gettext('auth.persistent_login_interval', 'How long a persistent login lasts.  E.g. ''2 weeks''', 'coust', 'description'),
2077     'interval'
2078 );
2079
2080 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2081         'cat.marc_control_number_identifier',
2082         oils_i18n_gettext(
2083             'cat.marc_control_number_identifier', 
2084             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2085             'coust', 
2086             'label'),
2087         oils_i18n_gettext(
2088             'cat.marc_control_number_identifier', 
2089             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2090             'coust', 
2091             'description'),
2092         'string'
2093 );
2094
2095 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) 
2096     VALUES (
2097         'circ.selfcheck.block_checkout_on_copy_status',
2098         oils_i18n_gettext(
2099             'circ.selfcheck.block_checkout_on_copy_status',
2100             'Selfcheck: Block copy checkout status',
2101             'coust',
2102             'label'
2103         ),
2104         oils_i18n_gettext(
2105             'circ.selfcheck.block_checkout_on_copy_status',
2106             'List of copy status IDs that will block checkout even if the generic COPY_NOT_AVAILABLE event is overridden',
2107             'coust',
2108             'description'
2109         ),
2110         'array'
2111     );
2112
2113 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2114 VALUES (
2115     'serial.prev_issuance_copy_location',
2116     oils_i18n_gettext(
2117         'serial.prev_issuance_copy_location',
2118         'Serials: Previous Issuance Copy Location',
2119         'coust',
2120         'label'
2121     ),
2122     oils_i18n_gettext(
2123         'serial.prev_issuance_copy_location',
2124         'When a serial issuance is received, copies (units) of the previous issuance will be automatically moved into the configured shelving location',
2125         'coust',
2126         'descripton'
2127         ),
2128     'link',
2129     'acpl'
2130 );
2131
2132 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2133 VALUES (
2134     'cat.default_classification_scheme',
2135     oils_i18n_gettext(
2136         'cat.default_classification_scheme',
2137         'Cataloging: Default Classification Scheme',
2138         'coust',
2139         'label'
2140     ),
2141     oils_i18n_gettext(
2142         'cat.default_classification_scheme',
2143         'Defines the default classification scheme for new call numbers: 1 = Generic; 2 = Dewey; 3 = LC',
2144         'coust',
2145         'descripton'
2146         ),
2147     'link',
2148     'acnc'
2149 );
2150
2151 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2152         'opac.org_unit_hiding.depth',
2153         oils_i18n_gettext(
2154             'opac.org_unit_hiding.depth',
2155             'OPAC: Org Unit Hiding Depth', 
2156             'coust', 
2157             'label'),
2158         oils_i18n_gettext(
2159             'opac.org_unit_hiding.depth',
2160             '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.', 
2161             'coust', 
2162             'description'),
2163         'integer'
2164 );
2165
2166 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2167     VALUES 
2168         ('circ.holds.alert_if_local_avail',
2169          'Holds: Local available alert',
2170          'If local copy is available, alert the person making the hold',
2171          'bool'),
2172
2173         ('circ.holds.deny_if_local_avail',
2174          'Holds: Local available block',
2175          'If local copy is available, deny the creation of the hold',
2176          'bool')
2177     ;
2178
2179 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2180     VALUES ( 
2181         'circ.holds.clear_shelf.no_capture_holds',
2182         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'Holds: Bypass hold capture during clear shelf process', 'coust', 'label'),
2183         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'During the clear shelf process, avoid capturing new holds on cleared items.', 'coust', 'description'),
2184         'bool'
2185     );
2186
2187 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
2188     'circ.booking_reservation.stop_circ',
2189     'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
2190     '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.',
2191     'bool'
2192 );
2193
2194 ---------------------------------
2195 -- Seed data for usr_setting_type
2196 ----------------------------------
2197
2198 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2199     VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
2200
2201 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2202     VALUES ('opac.default_search_depth', TRUE, 'OPAC Search Depth', 'OPAC Search Depth', 'integer');
2203
2204 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2205     VALUES ('opac.default_search_location', TRUE, 'OPAC Search Location', 'OPAC Search Location', 'integer');
2206
2207 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2208     VALUES ('opac.hits_per_page', TRUE, 'Hits per Page', 'Hits per Page', 'string');
2209
2210 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2211     VALUES ('opac.hold_notify', TRUE, 'Hold Notification Format', 'Hold Notification Format', 'string');
2212
2213 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2214     VALUES ('staff_client.catalog.record_view.default', TRUE, 'Default Record View', 'Default Record View', 'string');
2215
2216 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2217     VALUES ('staff_client.copy_editor.templates', TRUE, 'Copy Editor Template', 'Copy Editor Template', 'object');
2218
2219 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2220     VALUES ('circ.holds_behind_desk', FALSE, 'Hold is behind Circ Desk', 'Hold is behind Circ Desk', 'bool');
2221
2222 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2223     VALUES (
2224         'history.circ.retention_age',
2225         TRUE,
2226         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','label'),
2227         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','description'),
2228         'interval'
2229     ),(
2230         'history.circ.retention_start',
2231         FALSE,
2232         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','label'),
2233         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','description'),
2234         'date'
2235     );
2236
2237 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2238     VALUES (
2239         'history.hold.retention_age',
2240         TRUE,
2241         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','label'),
2242         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','description'),
2243         'interval'
2244     ),(
2245         'history.hold.retention_start',
2246         TRUE,
2247         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','label'),
2248         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','description'),
2249         'interval'
2250     ),(
2251         'history.hold.retention_count',
2252         TRUE,
2253         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','label'),
2254         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','description'),
2255         'integer'
2256     );
2257
2258 INSERT INTO config.usr_setting_type (name, opac_visible, label, description, datatype)
2259     VALUES (
2260         'opac.default_sort',
2261         TRUE,
2262         oils_i18n_gettext(
2263             'opac.default_sort',
2264             'OPAC Default Search Sort',
2265             'cust',
2266             'label'
2267         ),
2268         oils_i18n_gettext(
2269             'opac.default_sort',
2270             'OPAC Default Search Sort',
2271             'cust',
2272             'description'
2273         ),
2274         'string'
2275     );
2276
2277 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2278         'circ.missing_pieces.copy_status',
2279         oils_i18n_gettext(
2280             'circ.missing_pieces.copy_status',
2281             'Circulation: Item Status for Missing Pieces',
2282             'coust',
2283             'label'),
2284         oils_i18n_gettext(
2285             'circ.missing_pieces.copy_status',
2286             '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.',
2287             'coust',
2288             'description'),
2289         'link',
2290         'ccs'
2291 );
2292
2293 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2294         'circ.do_not_tally_claims_returned',
2295         oils_i18n_gettext(
2296             'circ.do_not_tally_claims_returned',
2297             'Circulation: Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.',
2298             'coust',
2299             'label'),
2300         oils_i18n_gettext(
2301             'circ.do_not_tally_claims_returned',
2302             '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.',
2303             'coust',
2304             'description'),
2305         'bool'
2306 );
2307
2308 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2309     VALUES
2310         ('cat.label.font.size',
2311             oils_i18n_gettext('cat.label.font.size',
2312                 'Cataloging: Spine and pocket label font size', 'coust', 'label'),
2313             oils_i18n_gettext('cat.label.font.size',
2314                 'Set the default font size for spine and pocket labels', 'coust', 'description'),
2315             'integer'
2316         )
2317         ,('cat.label.font.family',
2318             oils_i18n_gettext('cat.label.font.family',
2319                 'Cataloging: Spine and pocket label font family', 'coust', 'label'),
2320             oils_i18n_gettext('cat.label.font.family',
2321                 '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".',
2322                 'coust', 'description'),
2323             'string'
2324         )
2325         ,('cat.spine.line.width',
2326             oils_i18n_gettext('cat.spine.line.width',
2327                 'Cataloging: Spine label line width', 'coust', 'label'),
2328             oils_i18n_gettext('cat.spine.line.width',
2329                 'Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.',
2330                 'coust', 'description'),
2331             'integer'
2332         )
2333         ,('cat.spine.line.height',
2334             oils_i18n_gettext('cat.spine.line.height',
2335                 'Cataloging: Spine label maximum lines', 'coust', 'label'),
2336             oils_i18n_gettext('cat.spine.line.height',
2337                 'Set the default maximum number of lines for spine labels.',
2338                 'coust', 'description'),
2339             'integer'
2340         )
2341         ,('cat.spine.line.margin',
2342             oils_i18n_gettext('cat.spine.line.margin',
2343                 'Cataloging: Spine label left margin', 'coust', 'label'),
2344             oils_i18n_gettext('cat.spine.line.margin',
2345                 'Set the left margin for spine labels in number of characters.',
2346                 'coust', 'description'),
2347             'integer'
2348         )
2349 ;
2350
2351 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2352     VALUES
2353         ('cat.label.font.weight',
2354             oils_i18n_gettext('cat.label.font.weight',
2355                 'Cataloging: Spine and pocket label font weight', 'coust', 'label'),
2356             oils_i18n_gettext('cat.label.font.weight',
2357                 'Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".',
2358                 'coust', 'description'),
2359             'string'
2360         )
2361 ;
2362
2363 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2364         'circ.patron_edit.clone.copy_address',
2365         oils_i18n_gettext(
2366             'circ.patron_edit.clone.copy_address',
2367             'Patron Registration: Cloned patrons get address copy',
2368             'coust',
2369             'label'
2370         ),
2371         oils_i18n_gettext(
2372             'circ.patron_edit.clone.copy_address',
2373             'In the Patron editor, copy addresses from the cloned user instead of linking directly to the address',
2374             'coust',
2375             'description'
2376         ),
2377         'bool'
2378 );
2379
2380 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2381         'ui.patron.default_ident_type',
2382         oils_i18n_gettext(
2383             'ui.patron.default_ident_type',
2384             'GUI: Default Ident Type for Patron Registration',
2385             'coust',
2386             'label'),
2387         oils_i18n_gettext(
2388             'ui.patron.default_ident_type',
2389             'This is the default Ident Type for new users in the patron editor.',
2390             'coust',
2391             'description'),
2392         'link',
2393         'cit'
2394 );
2395
2396 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2397         'ui.patron.default_country',
2398         oils_i18n_gettext(
2399             'ui.patron.default_country',
2400             'GUI: Default Country for New Addresses in Patron Editor',
2401             'coust',
2402             'label'),
2403         oils_i18n_gettext(
2404             'ui.patron.default_country',
2405             'This is the default Country for new addresses in the patron editor.',
2406             'coust',
2407             'description'),
2408         'string'
2409 );
2410
2411 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2412         'ui.patron.registration.require_address',
2413         oils_i18n_gettext(
2414             'ui.patron.registration.require_address',
2415             'GUI: Require at least one address for Patron Registration',
2416             'coust',
2417             'label'),
2418         oils_i18n_gettext(
2419             'ui.patron.registration.require_address',
2420             'Enforces a requirement for having at least one address for a patron during registration.',
2421             'coust',
2422             'description'),
2423         'bool'
2424 );
2425
2426 INSERT INTO config.org_unit_setting_type (
2427     name, label, description, datatype
2428 ) VALUES
2429     ('credit.processor.payflowpro.enabled',
2430         'Credit card processing: Enable PayflowPro payments',
2431         'This is NOT the same thing as the settings labeled with just "PayPal."',
2432         'bool'
2433     ),
2434     ('credit.processor.payflowpro.login',
2435         'Credit card processing: PayflowPro login/merchant ID',
2436         'Often the same thing as the PayPal manager login',
2437         'string'
2438     ),
2439     ('credit.processor.payflowpro.password',
2440         'Credit card processing: PayflowPro password',
2441         'PayflowPro password',
2442         'string'
2443     ),
2444     ('credit.processor.payflowpro.testmode',
2445         'Credit card processing: PayflowPro test mode',
2446         'Do not really process transactions, but stay in test mode - uses pilot-payflowpro.paypal.com instead of the usual host',
2447         'bool'
2448     ),
2449     ('credit.processor.payflowpro.vendor',
2450         'Credit card processing: PayflowPro vendor',
2451         'Often the same thing as the login',
2452         'string'
2453     ),
2454     ('credit.processor.payflowpro.partner',
2455         'Credit card processing: PayflowPro partner',
2456         'Often "PayPal" or "VeriSign", sometimes others',
2457         'string'
2458     );
2459
2460 -- Patch the name of an old selfcheck setting
2461 UPDATE actor.org_unit_setting
2462     SET name = 'circ.selfcheck.alert.popup'
2463     WHERE name = 'circ.selfcheck.alert_on_checkout_event';
2464
2465 -- Rename certain existing org_unit settings, if present,
2466 -- and make sure their values are JSON
2467 UPDATE actor.org_unit_setting SET
2468     name = 'circ.holds.default_estimated_wait_interval',
2469     --
2470     -- The value column should be JSON.  The old value should be a number,
2471     -- but it may or may not be quoted.  The following CASE behaves
2472     -- differently depending on whether value is quoted.  It is simplistic,
2473     -- and will be defeated by leading or trailing white space, or various
2474     -- malformations.
2475     --
2476     value = CASE WHEN SUBSTR( value, 1, 1 ) = '"'
2477                 THEN '"' || SUBSTR( value, 2, LENGTH(value) - 2 ) || ' days"'
2478                 ELSE '"' || value || ' days"'
2479             END
2480 WHERE name = 'circ.hold_estimate_wait_interval';
2481
2482 -- Create types for existing org unit settings
2483 -- not otherwise accounted for
2484
2485 INSERT INTO config.org_unit_setting_type(
2486  name,
2487  label,
2488  description
2489 )
2490 SELECT DISTINCT
2491         name,
2492         name,
2493         'FIXME'
2494 FROM
2495         actor.org_unit_setting
2496 WHERE
2497         name NOT IN (
2498                 SELECT name
2499                 FROM config.org_unit_setting_type
2500         );
2501
2502 -- Add foreign key to org_unit_setting
2503
2504 ALTER TABLE actor.org_unit_setting
2505         ADD FOREIGN KEY (name) REFERENCES config.org_unit_setting_type (name)
2506                 DEFERRABLE INITIALLY DEFERRED;
2507
2508 -- Create types for existing user settings
2509 -- not otherwise accounted for
2510
2511 INSERT INTO config.usr_setting_type (
2512         name,
2513         label,
2514         description
2515 )
2516 SELECT DISTINCT
2517         name,
2518         name,
2519         'FIXME'
2520 FROM
2521         actor.usr_setting
2522 WHERE
2523         name NOT IN (
2524                 SELECT name
2525                 FROM config.usr_setting_type
2526         );
2527
2528 -- Add foreign key to user_setting_type
2529
2530 ALTER TABLE actor.usr_setting
2531         ADD FOREIGN KEY (name) REFERENCES config.usr_setting_type (name)
2532                 ON DELETE CASCADE ON UPDATE CASCADE
2533                 DEFERRABLE INITIALLY DEFERRED;
2534
2535 INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES
2536     (1, 'cat.spine.line.margin', 0)
2537     ,(1, 'cat.spine.line.height', 9)
2538     ,(1, 'cat.spine.line.width', 8)
2539     ,(1, 'cat.label.font.family', '"monospace"')
2540     ,(1, 'cat.label.font.size', 10)
2541     ,(1, 'cat.label.font.weight', '"normal"')
2542 ;
2543
2544 ALTER TABLE action_trigger.event_definition ADD COLUMN granularity TEXT;
2545 ALTER TABLE action_trigger.event ADD COLUMN async_output BIGINT REFERENCES action_trigger.event_output (id);
2546 ALTER TABLE action_trigger.event_definition ADD COLUMN usr_field TEXT;
2547 ALTER TABLE action_trigger.event_definition ADD COLUMN opt_in_setting TEXT REFERENCES config.usr_setting_type (name) DEFERRABLE INITIALLY DEFERRED;
2548
2549 CREATE OR REPLACE FUNCTION is_json( TEXT ) RETURNS BOOL AS $f$
2550     use JSON::XS;
2551     my $json = shift();
2552     eval { JSON::XS->new->allow_nonref->decode( $json ) };
2553     return $@ ? 0 : 1;
2554 $f$ LANGUAGE PLPERLU;
2555
2556 ALTER TABLE action_trigger.event ADD COLUMN user_data TEXT CHECK (user_data IS NULL OR is_json( user_data ));
2557
2558 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2559     'hold_request.cancel.expire_no_target',
2560     'ahr',
2561     'A hold is cancelled because no copies were found'
2562 );
2563
2564 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2565     'hold_request.cancel.expire_holds_shelf',
2566     'ahr',
2567     'A hold is cancelled because it was on the holds shelf too long'
2568 );
2569
2570 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2571     'hold_request.cancel.staff',
2572     'ahr',
2573     'A hold is cancelled because it was cancelled by staff'
2574 );
2575
2576 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2577     'hold_request.cancel.patron',
2578     'ahr',
2579     'A hold is cancelled by the patron'
2580 );
2581
2582 -- Fix typos in descriptions
2583 UPDATE action_trigger.hook SET description = 'A hold is successfully placed' WHERE key = 'hold_request.success';
2584 UPDATE action_trigger.hook SET description = 'A hold is attempted but not successfully placed' WHERE key = 'hold_request.failure';
2585
2586 -- Add a hook for renewals
2587 INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('renewal','circ','Item renewed to user');
2588
2589 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');
2590
2591 -- Sample Pre-due Notice --
2592
2593 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template) 
2594     VALUES (6, 'f', 1, '3 Day Courtesy Notice', 'checkout.due', 'CircIsOpen', 'SendEmail', '-3 days', 'due_date', 'usr', 
2595 $$
2596 [%- USE date -%]
2597 [%- user = target.0.usr -%]
2598 To: [%- params.recipient_email || user.email %]
2599 From: [%- params.sender_email || default_sender %]
2600 Subject: Courtesy Notice
2601
2602 Dear [% user.family_name %], [% user.first_given_name %]
2603 As a reminder, the following items are due in 3 days.
2604
2605 [% FOR circ IN target %]
2606     Title: [% circ.target_copy.call_number.record.simple_record.title %] 
2607     Barcode: [% circ.target_copy.barcode %] 
2608     Due: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2609     Item Cost: [% helpers.get_copy_price(circ.target_copy) %]
2610     Library: [% circ.circ_lib.name %]
2611     Library Phone: [% circ.circ_lib.phone %]
2612 [% END %]
2613
2614 $$);
2615
2616 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2617     (6, 'target_copy.call_number.record.simple_record'),
2618     (6, 'usr'),
2619     (6, 'circ_lib.billing_address');
2620
2621 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2622     (6, 'max_delay_age', '"1 day"');
2623
2624 -- also add the max delay age to the default overdue notice event def
2625 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2626     (1, 'max_delay_age', '"1 day"');
2627   
2628 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');
2629
2630 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.');
2631
2632 INSERT INTO action_trigger.hook (
2633         key,
2634         core_type,
2635         description,
2636         passive
2637     ) VALUES (
2638         'hold_request.shelf_expires_soon',
2639         'ahr',
2640         'A hold on the shelf will expire there soon.',
2641         TRUE
2642     );
2643
2644 INSERT INTO action_trigger.event_definition (
2645         id,
2646         active,
2647         owner,
2648         name,
2649         hook,
2650         validator,
2651         reactor,
2652         delay,
2653         delay_field,
2654         group_field,
2655         template
2656     ) VALUES (
2657         7,
2658         FALSE,
2659         1,
2660         'Hold Expires from Shelf Soon',
2661         'hold_request.shelf_expires_soon',
2662         'HoldIsAvailable',
2663         'SendEmail',
2664         '- 1 DAY',
2665         'shelf_expire_time',
2666         'usr',
2667 $$
2668 [%- USE date -%]
2669 [%- user = target.0.usr -%]
2670 To: [%- params.recipient_email || user.email %]
2671 From: [%- params.sender_email || default_sender %]
2672 Subject: Hold Available Notification
2673
2674 Dear [% user.family_name %], [% user.first_given_name %]
2675 You requested holds on the following item(s), which are available for
2676 pickup, but these holds will soon expire.
2677
2678 [% FOR hold IN target %]
2679     [%- data = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
2680     Title: [% data.title %]
2681     Author: [% data.author %]
2682     Library: [% hold.pickup_lib.name %]
2683 [% END %]
2684 $$
2685     );
2686
2687 INSERT INTO action_trigger.environment (
2688         event_def,
2689         path
2690     ) VALUES
2691     ( 7, 'current_copy'),
2692     ( 7, 'pickup_lib.billing_address'),
2693     ( 7, 'usr');
2694
2695 INSERT INTO action_trigger.hook (
2696         key,
2697         core_type,
2698         description,
2699         passive
2700     ) VALUES (
2701         'hold_request.long_wait',
2702         'ahr',
2703         'A patron has been waiting on a hold to be fulfilled for a long time.',
2704         TRUE
2705     );
2706
2707 INSERT INTO action_trigger.event_definition (
2708         id,
2709         active,
2710         owner,
2711         name,
2712         hook,
2713         validator,
2714         reactor,
2715         delay,
2716         delay_field,
2717         group_field,
2718         template
2719     ) VALUES (
2720         9,
2721         FALSE,
2722         1,
2723         'Hold waiting for pickup for long time',
2724         'hold_request.long_wait',
2725         'NOOP_True',
2726         'SendEmail',
2727         '6 MONTHS',
2728         'request_time',
2729         'usr',
2730 $$
2731 [%- USE date -%]
2732 [%- user = target.0.usr -%]
2733 To: [%- params.recipient_email || user.email %]
2734 From: [%- params.sender_email || default_sender %]
2735 Subject: Long Wait Hold Notification
2736
2737 Dear [% user.family_name %], [% user.first_given_name %]
2738
2739 You requested hold(s) on the following item(s), but unfortunately
2740 we have not been able to fulfill your request after a considerable
2741 length of time.  If you would still like to receive these items,
2742 no action is required.
2743
2744 [% FOR hold IN target %]
2745     Title: [% hold.bib_rec.bib_record.simple_record.title %]
2746     Author: [% hold.bib_rec.bib_record.simple_record.author %]
2747 [% END %]
2748 $$
2749 );
2750
2751 INSERT INTO action_trigger.environment (
2752         event_def,
2753         path
2754     ) VALUES
2755     (9, 'pickup_lib'),
2756     (9, 'usr'),
2757     (9, 'bib_rec.bib_record.simple_record');
2758
2759 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2760     VALUES (
2761         'format.selfcheck.checkout',
2762         'circ',
2763         'Formats circ objects for self-checkout receipt',
2764         TRUE
2765     );
2766
2767 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2768     VALUES (
2769         10,
2770         TRUE,
2771         1,
2772         'Self-Checkout Receipt',
2773         'format.selfcheck.checkout',
2774         'NOOP_True',
2775         'ProcessTemplate',
2776         'usr',
2777         'print-on-demand',
2778 $$
2779 [%- USE date -%]
2780 [%- SET user = target.0.usr -%]
2781 [%- SET lib = target.0.circ_lib -%]
2782 [%- SET lib_addr = target.0.circ_lib.billing_address -%]
2783 [%- SET hours = lib.hours_of_operation -%]
2784 <div>
2785     <style> li { padding: 8px; margin 5px; }</style>
2786     <div>[% date.format %]</div>
2787     <div>[% lib.name %]</div>
2788     <div>[% lib_addr.street1 %] [% lib_addr.street2 %]</div>
2789     <div>[% lib_addr.city %], [% lib_addr.state %] [% lb_addr.post_code %]</div>
2790     <div>[% lib.phone %]</div>
2791     <br/>
2792
2793     [% user.family_name %], [% user.first_given_name %]
2794     <ol>
2795     [% FOR circ IN target %]
2796         [%-
2797             SET idx = loop.count - 1;
2798             SET udata =  user_data.$idx
2799         -%]
2800         <li>
2801             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2802             <div>Barcode: [% circ.target_copy.barcode %]</div>
2803             [% IF udata.renewal_failure %]
2804                 <div style='color:red;'>Renewal Failed</div>
2805             [% ELSE %]
2806                 <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2807             [% END %]
2808         </li>
2809     [% END %]
2810     </ol>
2811     
2812     <div>
2813         Library Hours
2814         [%- BLOCK format_time; date.format(time _ ' 1/1/1000', format='%I:%M %p'); END -%]
2815         <div>
2816             Monday 
2817             [% PROCESS format_time time = hours.dow_0_open %] 
2818             [% PROCESS format_time time = hours.dow_0_close %] 
2819         </div>
2820         <div>
2821             Tuesday 
2822             [% PROCESS format_time time = hours.dow_1_open %] 
2823             [% PROCESS format_time time = hours.dow_1_close %] 
2824         </div>
2825         <div>
2826             Wednesday 
2827             [% PROCESS format_time time = hours.dow_2_open %] 
2828             [% PROCESS format_time time = hours.dow_2_close %] 
2829         </div>
2830         <div>
2831             Thursday
2832             [% PROCESS format_time time = hours.dow_3_open %] 
2833             [% PROCESS format_time time = hours.dow_3_close %] 
2834         </div>
2835         <div>
2836             Friday
2837             [% PROCESS format_time time = hours.dow_4_open %] 
2838             [% PROCESS format_time time = hours.dow_4_close %] 
2839         </div>
2840         <div>
2841             Saturday
2842             [% PROCESS format_time time = hours.dow_5_open %] 
2843             [% PROCESS format_time time = hours.dow_5_close %] 
2844         </div>
2845         <div>
2846             Sunday 
2847             [% PROCESS format_time time = hours.dow_6_open %] 
2848             [% PROCESS format_time time = hours.dow_6_close %] 
2849         </div>
2850     </div>
2851 </div>
2852 $$
2853 );
2854
2855 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2856     ( 10, 'target_copy'),
2857     ( 10, 'circ_lib.billing_address'),
2858     ( 10, 'circ_lib.hours_of_operation'),
2859     ( 10, 'usr');
2860
2861 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2862     VALUES (
2863         'format.selfcheck.items_out',
2864         'circ',
2865         'Formats items out for self-checkout receipt',
2866         TRUE
2867     );
2868
2869 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2870     VALUES (
2871         11,
2872         TRUE,
2873         1,
2874         'Self-Checkout Items Out Receipt',
2875         'format.selfcheck.items_out',
2876         'NOOP_True',
2877         'ProcessTemplate',
2878         'usr',
2879         'print-on-demand',
2880 $$
2881 [%- USE date -%]
2882 [%- SET user = target.0.usr -%]
2883 <div>
2884     <style> li { padding: 8px; margin 5px; }</style>
2885     <div>[% date.format %]</div>
2886     <br/>
2887
2888     [% user.family_name %], [% user.first_given_name %]
2889     <ol>
2890     [% FOR circ IN target %]
2891         <li>
2892             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2893             <div>Barcode: [% circ.target_copy.barcode %]</div>
2894             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2895         </li>
2896     [% END %]
2897     </ol>
2898 </div>
2899 $$
2900 );
2901
2902
2903 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2904     ( 11, 'target_copy'),
2905     ( 11, 'circ_lib.billing_address'),
2906     ( 11, 'circ_lib.hours_of_operation'),
2907     ( 11, 'usr');
2908
2909 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2910     VALUES (
2911         'format.selfcheck.holds',
2912         'ahr',
2913         'Formats holds for self-checkout receipt',
2914         TRUE
2915     );
2916
2917 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2918     VALUES (
2919         12,
2920         TRUE,
2921         1,
2922         'Self-Checkout Holds Receipt',
2923         'format.selfcheck.holds',
2924         'NOOP_True',
2925         'ProcessTemplate',
2926         'usr',
2927         'print-on-demand',
2928 $$
2929 [%- USE date -%]
2930 [%- SET user = target.0.usr -%]
2931 <div>
2932     <style> li { padding: 8px; margin 5px; }</style>
2933     <div>[% date.format %]</div>
2934     <br/>
2935
2936     [% user.family_name %], [% user.first_given_name %]
2937     <ol>
2938     [% FOR hold IN target %]
2939         [%-
2940             SET idx = loop.count - 1;
2941             SET udata =  user_data.$idx
2942         -%]
2943         <li>
2944             <div>Title: [% hold.bib_rec.bib_record.simple_record.title %]</div>
2945             <div>Author: [% hold.bib_rec.bib_record.simple_record.author %]</div>
2946             <div>Pickup Location: [% hold.pickup_lib.name %]</div>
2947             <div>Status: 
2948                 [%- IF udata.ready -%]
2949                     Ready for pickup
2950                 [% ELSE %]
2951                     #[% udata.queue_position %] of [% udata.potential_copies %] copies.
2952                 [% END %]
2953             </div>
2954         </li>
2955     [% END %]
2956     </ol>
2957 </div>
2958 $$
2959 );
2960
2961
2962 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2963     ( 12, 'bib_rec.bib_record.simple_record'),
2964     ( 12, 'pickup_lib'),
2965     ( 12, 'usr');
2966
2967 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2968     VALUES (
2969         'format.selfcheck.fines',
2970         'au',
2971         'Formats fines for self-checkout receipt',
2972         TRUE
2973     );
2974
2975 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template )
2976     VALUES (
2977         13,
2978         TRUE,
2979         1,
2980         'Self-Checkout Fines Receipt',
2981         'format.selfcheck.fines',
2982         'NOOP_True',
2983         'ProcessTemplate',
2984         'print-on-demand',
2985 $$
2986 [%- USE date -%]
2987 [%- SET user = target -%]
2988 <div>
2989     <style> li { padding: 8px; margin 5px; }</style>
2990     <div>[% date.format %]</div>
2991     <br/>
2992
2993     [% user.family_name %], [% user.first_given_name %]
2994     <ol>
2995     [% FOR xact IN user.open_billable_transactions_summary %]
2996         <li>
2997             <div>Details: 
2998                 [% IF xact.xact_type == 'circulation' %]
2999                     [%- helpers.get_copy_bib_basics(xact.circulation.target_copy).title -%]
3000                 [% ELSE %]
3001                     [%- xact.last_billing_type -%]
3002                 [% END %]
3003             </div>
3004             <div>Total Billed: [% xact.total_owed %]</div>
3005             <div>Total Paid: [% xact.total_paid %]</div>
3006             <div>Balance Owed : [% xact.balance_owed %]</div>
3007         </li>
3008     [% END %]
3009     </ol>
3010 </div>
3011 $$
3012 );
3013
3014 INSERT INTO action_trigger.environment ( event_def, path) VALUES
3015     ( 13, 'open_billable_transactions_summary.circulation' );
3016
3017 INSERT INTO action_trigger.reactor (module,description) VALUES
3018 (   'SendFile',
3019     oils_i18n_gettext(
3020         'SendFile',
3021         '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.',
3022         'atreact',
3023         'description'
3024     )
3025 );
3026
3027 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
3028     VALUES (
3029         'format.acqli.html',
3030         'jub',
3031         'Formats lineitem worksheet for titles received',
3032         TRUE
3033     );
3034
3035 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template)
3036     VALUES (
3037         14,
3038         TRUE,
3039         1,
3040         'Lineitem Worksheet',
3041         'format.acqli.html',
3042         'NOOP_True',
3043         'ProcessTemplate',
3044         'print-on-demand',
3045 $$
3046 [%- USE date -%]
3047 [%- SET li = target; -%]
3048 <div class="wrapper">
3049     <div class="summary" style='font-size:110%; font-weight:bold;'>
3050
3051         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
3052         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
3053         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
3054         <div class="lineid">Lineitem ID: [% li.id %]</div>
3055
3056         [% IF li.distribution_formulas.size > 0 %]
3057             [% SET forms = [] %]
3058             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
3059             <div>Distribution Formulas: [% forms.join(',') %]</div>
3060         [% END %]
3061
3062         [% IF li.lineitem_notes.size > 0 %]
3063             Lineitem Notes:
3064             <ul>
3065                 [%- FOR note IN li.lineitem_notes -%]
3066                     <li>
3067                     [% IF note.alert_text %]
3068                         [% note.alert_text.code -%] 
3069                         [% IF note.value -%]
3070                             : [% note.value %]
3071                         [% END %]
3072                     [% ELSE %]
3073                         [% note.value -%] 
3074                     [% END %]
3075                     </li>
3076                 [% END %]
3077             </ul>
3078         [% END %]
3079     </div>
3080     <br/>
3081     <table>
3082         <thead>
3083             <tr>
3084                 <th>Branch</th>
3085                 <th>Barcode</th>
3086                 <th>Call Number</th>
3087                 <th>Fund</th>
3088                 <th>Recd.</th>
3089                 <th>Notes</th>
3090             </tr>
3091         </thead>
3092         <tbody>
3093         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
3094             [% 
3095                 IF copy.eg_copy_id;
3096                     SET copy = copy.eg_copy_id;
3097                     SET cn_label = copy.call_number.label;
3098                 ELSE; 
3099                     SET copy = detail; 
3100                     SET cn_label = detail.cn_label;
3101                 END 
3102             %]
3103             <tr>
3104                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
3105                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
3106                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
3107                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
3108                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
3109                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
3110                 <td style='padding:5px;'>[% detail.note %]</td>
3111             </tr>
3112         [% END %]
3113         </tbody>
3114     </table>
3115 </div>
3116 $$
3117 );
3118
3119
3120 INSERT INTO action_trigger.environment (event_def, path) VALUES
3121     ( 14, 'attributes' ),
3122     ( 14, 'lineitem_details' ),
3123     ( 14, 'lineitem_details.owning_lib' ),
3124     ( 14, 'lineitem_notes' )
3125 ;
3126
3127 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3128         'aur.ordered',
3129         'aur', 
3130         oils_i18n_gettext(
3131             'aur.ordered',
3132             'A patron acquisition request has been marked On-Order.',
3133             'ath',
3134             'description'
3135         ), 
3136         TRUE
3137     ), (
3138         'aur.received', 
3139         'aur', 
3140         oils_i18n_gettext(
3141             'aur.received', 
3142             'A patron acquisition request has been marked Received.',
3143             'ath',
3144             'description'
3145         ),
3146         TRUE
3147     ), (
3148         'aur.cancelled',
3149         'aur',
3150         oils_i18n_gettext(
3151             'aur.cancelled',
3152             'A patron acquisition request has been marked Cancelled.',
3153             'ath',
3154             'description'
3155         ),
3156         TRUE
3157     )
3158 ;
3159
3160 INSERT INTO action_trigger.validator (module,description) VALUES (
3161         'Acq::UserRequestOrdered',
3162         oils_i18n_gettext(
3163             'Acq::UserRequestOrdered',
3164             'Tests to see if the corresponding Line Item has a state of "on-order".',
3165             'atval',
3166             'description'
3167         )
3168     ), (
3169         'Acq::UserRequestReceived',
3170         oils_i18n_gettext(
3171             'Acq::UserRequestReceived',
3172             'Tests to see if the corresponding Line Item has a state of "received".',
3173             'atval',
3174             'description'
3175         )
3176     ), (
3177         'Acq::UserRequestCancelled',
3178         oils_i18n_gettext(
3179             'Acq::UserRequestCancelled',
3180             'Tests to see if the corresponding Line Item has a state of "cancelled".',
3181             'atval',
3182             'description'
3183         )
3184     )
3185 ;
3186
3187 -- What was event_definition #15 in v1.6.1 will be recreated as #20.  This
3188 -- renumbering requires some juggling:
3189 --
3190 -- 1. Update any child rows to point to #20.  These updates will temporarily
3191 -- violate foreign key constraints, but that's okay as long as we create
3192 -- #20 before committing.
3193 --
3194 -- 2. Delete the old #15.
3195 --
3196 -- 3. Insert the new #15.
3197 --
3198 -- 4. Insert #20.
3199 --
3200 -- We could combine steps 2 and 3 into a single update, but that would create
3201 -- additional opportunities for typos, since we already have the insert from
3202 -- an upgrade script.
3203
3204 UPDATE action_trigger.environment
3205 SET event_def = 20
3206 WHERE event_def = 15;
3207
3208 UPDATE action_trigger.event
3209 SET event_def = 20
3210 WHERE event_def = 15;
3211
3212 UPDATE action_trigger.event_params
3213 SET event_def = 20
3214 WHERE event_def = 15;
3215
3216 DELETE FROM action_trigger.event_definition
3217 WHERE id = 15;
3218
3219 INSERT INTO action_trigger.event_definition (
3220         id,
3221         active,
3222         owner,
3223         name,
3224         hook,
3225         validator,
3226         reactor,
3227         template
3228     ) VALUES (
3229         15,
3230         FALSE,
3231         1,
3232         'Email Notice: Patron Acquisition Request marked On-Order.',
3233         'aur.ordered',
3234         'Acq::UserRequestOrdered',
3235         'SendEmail',
3236 $$
3237 [%- USE date -%]
3238 [%- SET li = target.lineitem; -%]
3239 [%- SET user = target.usr -%]
3240 [%- SET title = helpers.get_li_attr("title", "", li.attributes) -%]
3241 [%- SET author = helpers.get_li_attr("author", "", li.attributes) -%]
3242 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) -%]
3243 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) -%]
3244 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3245 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3246
3247 To: [%- params.recipient_email || user.email %]
3248 From: [%- params.sender_email || default_sender %]
3249 Subject: Acquisition Request Notification
3250
3251 Dear [% user.family_name %], [% user.first_given_name %]
3252 Our records indicate the following acquisition request has been placed on order.
3253
3254 Title: [% title %]
3255 [% IF author %]Author: [% author %][% END %]
3256 [% IF edition %]Edition: [% edition %][% END %]
3257 [% IF isbn %]ISBN: [% isbn %][% END %]
3258 [% IF publisher %]Publisher: [% publisher %][% END %]
3259 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3260 Lineitem ID: [% li.id %]
3261 $$
3262     ), (
3263         16,
3264         FALSE,
3265         1,
3266         'Email Notice: Patron Acquisition Request marked Received.',
3267         'aur.received',
3268         'Acq::UserRequestReceived',
3269         'SendEmail',
3270 $$
3271 [%- USE date -%]
3272 [%- SET li = target.lineitem; -%]
3273 [%- SET user = target.usr -%]
3274 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3275 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3276 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3277 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3278 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3279 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3280
3281 To: [%- params.recipient_email || user.email %]
3282 From: [%- params.sender_email || default_sender %]
3283 Subject: Acquisition Request Notification
3284
3285 Dear [% user.family_name %], [% user.first_given_name %]
3286 Our records indicate the materials for the following acquisition request have been received.
3287
3288 Title: [% title %]
3289 [% IF author %]Author: [% author %][% END %]
3290 [% IF edition %]Edition: [% edition %][% END %]
3291 [% IF isbn %]ISBN: [% isbn %][% END %]
3292 [% IF publisher %]Publisher: [% publisher %][% END %]
3293 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3294 Lineitem ID: [% li.id %]
3295 $$
3296     ), (
3297         17,
3298         FALSE,
3299         1,
3300         'Email Notice: Patron Acquisition Request marked Cancelled.',
3301         'aur.cancelled',
3302         'Acq::UserRequestCancelled',
3303         'SendEmail',
3304 $$
3305 [%- USE date -%]
3306 [%- SET li = target.lineitem; -%]
3307 [%- SET user = target.usr -%]
3308 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3309 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3310 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3311 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3312 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3313 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3314
3315 To: [%- params.recipient_email || user.email %]
3316 From: [%- params.sender_email || default_sender %]
3317 Subject: Acquisition Request Notification
3318
3319 Dear [% user.family_name %], [% user.first_given_name %]
3320 Our records indicate the following acquisition request has been cancelled.
3321
3322 Title: [% title %]
3323 [% IF author %]Author: [% author %][% END %]
3324 [% IF edition %]Edition: [% edition %][% END %]
3325 [% IF isbn %]ISBN: [% isbn %][% END %]
3326 [% IF publisher %]Publisher: [% publisher %][% END %]
3327 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3328 Lineitem ID: [% li.id %]
3329 $$
3330     );
3331
3332 INSERT INTO action_trigger.environment (
3333         event_def,
3334         path
3335     ) VALUES 
3336         ( 15, 'lineitem' ),
3337         ( 15, 'lineitem.attributes' ),
3338         ( 15, 'usr' ),
3339
3340         ( 16, 'lineitem' ),
3341         ( 16, 'lineitem.attributes' ),
3342         ( 16, 'usr' ),
3343
3344         ( 17, 'lineitem' ),
3345         ( 17, 'lineitem.attributes' ),
3346         ( 17, 'usr' )
3347     ;
3348
3349 INSERT INTO action_trigger.event_definition
3350 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
3351 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
3352 $$
3353 [%- USE date -%]
3354 [%# start JEDI document 
3355   # Vendor specific kludges:
3356   # BT      - vendcode goes to NAD/BY *suffix*  w/ 91 qualifier
3357   # INGRAM  - vendcode goes to NAD/BY *segment* w/ 91 qualifier (separately)
3358   # BRODART - vendcode goes to FTX segment (lineitem level)
3359 -%]
3360 [%- 
3361 IF target.provider.edi_default.vendcode && target.provider.code == 'BRODART';
3362     xtra_ftx = target.provider.edi_default.vendcode;
3363 END;
3364 -%]
3365 [%- BLOCK big_block -%]
3366 {
3367    "recipient":"[% target.provider.san %]",
3368    "sender":"[% target.ordering_agency.mailing_address.san %]",
3369    "body": [{
3370      "ORDERS":[ "order", {
3371         "po_number":[% target.id %],
3372         "date":"[% date.format(date.now, '%Y%m%d') %]",
3373         "buyer":[
3374             [%   IF   target.provider.edi_default.vendcode && (target.provider.code == 'BT' || target.provider.name.match('(?i)^BAKER & TAYLOR'))  -%]
3375                 {"id-qualifier": 91, "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]"}
3376             [%- ELSIF target.provider.edi_default.vendcode && target.provider.code == 'INGRAM' -%]
3377                 {"id":"[% target.ordering_agency.mailing_address.san %]"},
3378                 {"id-qualifier": 91, "id":"[% target.provider.edi_default.vendcode %]"}
3379             [%- ELSE -%]
3380                 {"id":"[% target.ordering_agency.mailing_address.san %]"}
3381             [%- END -%]
3382         ],
3383         "vendor":[
3384             [%- # target.provider.name (target.provider.id) -%]
3385             "[% target.provider.san %]",
3386             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
3387         ],
3388         "currency":"[% target.provider.currency_type %]",
3389                 
3390         "items":[
3391         [%- FOR li IN target.lineitems %]
3392         {
3393             "line_index":"[% li.id %]",
3394             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
3395             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
3396                 [% IF isbn.length == 13 -%]
3397                 {"id-qualifier":"EN","id":"[% isbn %]"},
3398                 [% ELSE -%]
3399                 {"id-qualifier":"IB","id":"[% isbn %]"},
3400                 [%- END %]
3401             [% END %]
3402                 {"id-qualifier":"IN","id":"[% li.id %]"}
3403             ],
3404             "price":[% li.estimated_unit_price || '0.00' %],
3405             "desc":[
3406                 {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"},
3407                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
3408                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
3409                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
3410             ],
3411             [%- ftx_vals = []; 
3412                 FOR note IN li.lineitem_notes; 
3413                     NEXT UNLESS note.vendor_public == 't'; 
3414                     ftx_vals.push(note.value); 
3415                 END; 
3416                 IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
3417                 IF ftx_vals.size == 0; ftx_vals.unshift('');       END;  # BT needs FTX+LIN for every LI, even if it is an empty one
3418             -%]
3419
3420             "free-text":[ 
3421                 [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
3422             ],            
3423             "quantity":[% li.lineitem_details.size %]
3424         }[% UNLESS loop.last %],[% END %]
3425         [%-# TODO: lineitem details (later) -%]
3426         [% END %]
3427         ],
3428         "line_items":[% target.lineitems.size %]
3429      }]  [%# close ORDERS array %]
3430    }]    [%# close  body  array %]
3431 }
3432 [% END %]
3433 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
3434
3435 $$);
3436
3437 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3438   (23, 'lineitems.attributes'), 
3439   (23, 'lineitems.lineitem_details'), 
3440   (23, 'lineitems.lineitem_notes'), 
3441   (23, 'ordering_agency.mailing_address'), 
3442   (23, 'provider');
3443
3444 UPDATE action_trigger.event_definition SET template = 
3445 $$
3446 [%- USE date -%]
3447 [%-
3448     # find a lineitem attribute by name and optional type
3449     BLOCK get_li_attr;
3450         FOR attr IN li.attributes;
3451             IF attr.attr_name == attr_name;
3452                 IF !attr_type OR attr_type == attr.attr_type;
3453                     attr.attr_value;
3454                     LAST;
3455                 END;
3456             END;
3457         END;
3458     END
3459 -%]
3460
3461 <h2>Purchase Order [% target.id %]</h2>
3462 <br/>
3463 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
3464 <br/>
3465
3466 <style>
3467     table td { padding:5px; border:1px solid #aaa;}
3468     table { width:95%; border-collapse:collapse; }
3469     #vendor-notes { padding:5px; border:1px solid #aaa; }
3470 </style>
3471 <table id='vendor-table'>
3472   <tr>
3473     <td valign='top'>Vendor</td>
3474     <td>
3475       <div>[% target.provider.name %]</div>
3476       <div>[% target.provider.addresses.0.street1 %]</div>
3477       <div>[% target.provider.addresses.0.street2 %]</div>
3478       <div>[% target.provider.addresses.0.city %]</div>
3479       <div>[% target.provider.addresses.0.state %]</div>
3480       <div>[% target.provider.addresses.0.country %]</div>
3481       <div>[% target.provider.addresses.0.post_code %]</div>
3482     </td>
3483     <td valign='top'>Ship to / Bill to</td>
3484     <td>
3485       <div>[% target.ordering_agency.name %]</div>
3486       <div>[% target.ordering_agency.billing_address.street1 %]</div>
3487       <div>[% target.ordering_agency.billing_address.street2 %]</div>
3488       <div>[% target.ordering_agency.billing_address.city %]</div>
3489       <div>[% target.ordering_agency.billing_address.state %]</div>
3490       <div>[% target.ordering_agency.billing_address.country %]</div>
3491       <div>[% target.ordering_agency.billing_address.post_code %]</div>
3492     </td>
3493   </tr>
3494 </table>
3495
3496 <br/><br/>
3497 <fieldset id='vendor-notes'>
3498     <legend>Notes to the Vendor</legend>
3499     <ul>
3500     [% FOR note IN target.notes %]
3501         [% IF note.vendor_public == 't' %]
3502             <li>[% note.value %]</li>
3503         [% END %]
3504     [% END %]
3505     </ul>
3506 </fieldset>
3507 <br/><br/>
3508
3509 <table>
3510   <thead>
3511     <tr>
3512       <th>PO#</th>
3513       <th>ISBN or Item #</th>
3514       <th>Title</th>
3515       <th>Quantity</th>
3516       <th>Unit Price</th>
3517       <th>Line Total</th>
3518       <th>Notes</th>
3519     </tr>
3520   </thead>
3521   <tbody>
3522
3523   [% subtotal = 0 %]
3524   [% FOR li IN target.lineitems %]
3525
3526   <tr>
3527     [% count = li.lineitem_details.size %]
3528     [% price = li.estimated_unit_price %]
3529     [% litotal = (price * count) %]
3530     [% subtotal = subtotal + litotal %]
3531     [% isbn = PROCESS get_li_attr attr_name = 'isbn' %]
3532     [% ident = PROCESS get_li_attr attr_name = 'identifier' %]
3533
3534     <td>[% target.id %]</td>
3535     <td>[% isbn || ident %]</td>
3536     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
3537     <td>[% count %]</td>
3538     <td>[% price %]</td>
3539     <td>[% litotal %]</td>
3540     <td>
3541         <ul>
3542         [% FOR note IN li.lineitem_notes %]
3543             [% IF note.vendor_public == 't' %]
3544                 <li>[% note.value %]</li>
3545             [% END %]
3546         [% END %]
3547         </ul>
3548     </td>
3549   </tr>
3550   [% END %]
3551   <tr>
3552     <td/><td/><td/><td/>
3553     <td>Subtotal</td>
3554     <td>[% subtotal %]</td>
3555   </tr>
3556   </tbody>
3557 </table>
3558
3559 <br/>
3560
3561 Total Line Item Count: [% target.lineitems.size %]
3562 $$
3563 WHERE id = 4;
3564
3565 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3566     (4, 'lineitems.lineitem_notes'),
3567     (4, 'notes');
3568
3569 INSERT INTO action_trigger.environment (event_def, path) VALUES
3570     ( 14, 'lineitem_notes.alert_text' ),
3571     ( 14, 'distribution_formulas.formula' ),
3572     ( 14, 'lineitem_details.fund' ),
3573     ( 14, 'lineitem_details.eg_copy_id' ),
3574     ( 14, 'lineitem_details.eg_copy_id.call_number' )
3575 ;
3576
3577 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3578         'aur.created',
3579         'aur',
3580         oils_i18n_gettext(
3581             'aur.created',
3582             'A patron has made an acquisitions request.',
3583             'ath',
3584             'description'
3585         ),
3586         TRUE
3587     ), (
3588         'aur.rejected',
3589         'aur',
3590         oils_i18n_gettext(
3591             'aur.rejected',
3592             'A patron acquisition request has been rejected.',
3593             'ath',
3594             'description'
3595         ),
3596         TRUE
3597     )
3598 ;
3599
3600 INSERT INTO action_trigger.event_definition (
3601         id,
3602         active,
3603         owner,
3604         name,
3605         hook,
3606         validator,
3607         reactor,
3608         template
3609     ) VALUES (
3610         18,
3611         FALSE,
3612         1,
3613         'Email Notice: Acquisition Request created.',
3614         'aur.created',
3615         'NOOP_True',
3616         'SendEmail',
3617 $$
3618 [%- USE date -%]
3619 [%- SET user = target.usr -%]
3620 [%- SET title = target.title -%]
3621 [%- SET author = target.author -%]
3622 [%- SET isxn = target.isxn -%]
3623 [%- SET publisher = target.publisher -%]
3624 [%- SET pubdate = target.pubdate -%]
3625
3626 To: [%- params.recipient_email || user.email %]
3627 From: [%- params.sender_email || default_sender %]
3628 Subject: Acquisition Request Notification
3629
3630 Dear [% user.family_name %], [% user.first_given_name %]
3631 Our records indicate that you have made the following acquisition request:
3632
3633 Title: [% title %]
3634 [% IF author %]Author: [% author %][% END %]
3635 [% IF edition %]Edition: [% edition %][% END %]
3636 [% IF isbn %]ISXN: [% isxn %][% END %]
3637 [% IF publisher %]Publisher: [% publisher %][% END %]
3638 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3639 $$
3640     ), (
3641         19,
3642         FALSE,
3643         1,
3644         'Email Notice: Acquisition Request Rejected.',
3645         'aur.rejected',
3646         'NOOP_True',
3647         'SendEmail',
3648 $$
3649 [%- USE date -%]
3650 [%- SET user = target.usr -%]
3651 [%- SET title = target.title -%]
3652 [%- SET author = target.author -%]
3653 [%- SET isxn = target.isxn -%]
3654 [%- SET publisher = target.publisher -%]
3655 [%- SET pubdate = target.pubdate -%]
3656 [%- SET cancel_reason = target.cancel_reason.description -%]
3657
3658 To: [%- params.recipient_email || user.email %]
3659 From: [%- params.sender_email || default_sender %]
3660 Subject: Acquisition Request Notification
3661
3662 Dear [% user.family_name %], [% user.first_given_name %]
3663 Our records indicate the following acquisition request has been rejected for this reason: [% cancel_reason %]
3664
3665 Title: [% title %]
3666 [% IF author %]Author: [% author %][% END %]
3667 [% IF edition %]Edition: [% edition %][% END %]
3668 [% IF isbn %]ISBN: [% isbn %][% END %]
3669 [% IF publisher %]Publisher: [% publisher %][% END %]
3670 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3671 $$
3672     );
3673
3674 INSERT INTO action_trigger.environment (
3675         event_def,
3676         path
3677     ) VALUES 
3678         ( 18, 'usr' ),
3679         ( 19, 'usr' ),
3680         ( 19, 'cancel_reason' )
3681     ;
3682
3683 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, template) 
3684     VALUES (20, 'f', 1, 'Password reset request notification', 'password.reset_request', 'NOOP_True', 'SendEmail', '00:00:01',
3685 $$
3686 [%- USE date -%]
3687 [%- user = target.usr -%]
3688 To: [%- params.recipient_email || user.email %]
3689 From: [%- params.sender_email || user.home_ou.email || default_sender %]
3690 Subject: [% user.home_ou.name %]: library account password reset request
3691   
3692 You have received this message because you, or somebody else, requested a reset
3693 of your library system password. If you did not request a reset of your library
3694 system password, just ignore this message and your current password will
3695 continue to work.
3696
3697 If you did request a reset of your library system password, please perform
3698 the following steps to continue the process of resetting your password:
3699
3700 1. Open the following link in a web browser: https://[% params.hostname %]/opac/password/[% params.locale || 'en-US' %]/[% target.uuid %]
3701 The browser displays a password reset form.
3702
3703 2. Enter your new password in the password reset form in the browser. You must
3704 enter the password twice to ensure that you do not make a mistake. If the
3705 passwords match, you will then be able to log in to your library system account
3706 with the new password.
3707
3708 $$);
3709
3710 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3711     VALUES (
3712         'format.acqcle.html',
3713         'acqcle',
3714         'Formats claim events into a voucher',
3715         TRUE
3716     );
3717
3718 INSERT INTO action_trigger.event_definition (
3719         id, active, owner, name, hook, group_field,
3720         validator, reactor, granularity, template
3721     ) VALUES (
3722         21,
3723         TRUE,
3724         1,
3725         'Claim Voucher',
3726         'format.acqcle.html',
3727         'claim',
3728         'NOOP_True',
3729         'ProcessTemplate',
3730         'print-on-demand',
3731 $$
3732 [%- USE date -%]
3733 [%- SET claim = target.0.claim -%]
3734 <!-- This will need refined/prettified. -->
3735 <div class="acq-claim-voucher">
3736     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
3737     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
3738     <ul>
3739         [% FOR event IN target %]
3740         <li>
3741             Event type: [% event.type.code %]
3742             [% IF event.type.library_initiated %](Library initiated)[% END %]
3743             <br />
3744             Event date: [% event.event_date %]<br />
3745             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
3746             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
3747             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
3748             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
3749             [% event.claim.lineitem_detail.fund.code %]
3750             ([% event.claim.lineitem_detail.fund.year %])
3751         </li>
3752         [% END %]
3753     </ul>
3754 </div>
3755 $$
3756 );
3757
3758 INSERT INTO action_trigger.environment (event_def, path) VALUES
3759     (21, 'claim'),
3760     (21, 'claim.type'),
3761     (21, 'claim.lineitem_detail'),
3762     (21, 'claim.lineitem_detail.fund'),
3763     (21, 'claim.lineitem_detail.lineitem.attributes'),
3764     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
3765     (21, 'creator'),
3766     (21, 'type')
3767 ;
3768
3769 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3770     VALUES (
3771         'format.acqinv.html',
3772         'acqinv',
3773         'Formats invoices into a voucher',
3774         TRUE
3775     );
3776
3777 INSERT INTO action_trigger.event_definition (
3778         id, active, owner, name, hook,
3779         validator, reactor, granularity, template
3780     ) VALUES (
3781         22,
3782         TRUE,
3783         1,
3784         'Invoice',
3785         'format.acqinv.html',
3786         'NOOP_True',
3787         'ProcessTemplate',
3788         'print-on-demand',
3789 $$
3790 [% FILTER collapse %]
3791 [%- SET invoice = target -%]
3792 <!-- This lacks totals, info about funds (for invoice entries,
3793     funds are per-LID!), and general refinement -->
3794 <div class="acq-invoice-voucher">
3795     <h1>Invoice</h1>
3796     <div>
3797         <strong>No.</strong> [% invoice.inv_ident %]
3798         [% IF invoice.inv_type %]
3799             / <strong>Type:</strong>[% invoice.inv_type %]
3800         [% END %]
3801     </div>
3802     <div>
3803         <dl>
3804             [% BLOCK ent_with_address %]
3805             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3806             <dd>
3807                 [% IF ent.addresses.0 %]
3808                     [% SET addr = ent.addresses.0 %]
3809                     [% addr.street1 %]<br />
3810                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3811                     [% addr.city %],
3812                     [% IF addr.county %] [% addr.county %], [% END %]
3813                     [% IF addr.state %] [% addr.state %] [% END %]
3814                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3815                     [% IF addr.country %] [% addr.country %] [% END %]
3816                 [% END %]
3817                 <p>
3818                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3819                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3820                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3821                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3822                 </p>
3823             </dd>
3824             [% END %]
3825             [% INCLUDE ent_with_address
3826                 ent = invoice.provider
3827                 ent_label = "Provider" %]
3828             [% INCLUDE ent_with_address
3829                 ent = invoice.shipper
3830                 ent_label = "Shipper" %]
3831             <dt>Receiver</dt>
3832             <dd>
3833                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3834             </dd>
3835             <dt>Received</dt>
3836             <dd>
3837                 [% helpers.format_date(invoice.recv_date) %] by
3838                 [% invoice.recv_method %]
3839             </dd>
3840             [% IF invoice.note %]
3841                 <dt>Note</dt>
3842                 <dd>
3843                     [% invoice.note %]
3844                 </dd>
3845             [% END %]
3846         </dl>
3847     </div>
3848     <ul>
3849         [% FOR entry IN invoice.entries %]
3850             <li>
3851                 [% IF entry.lineitem %]
3852                     Title: [% helpers.get_li_attr(
3853                         "title", "", entry.lineitem.attributes
3854                     ) %]<br />
3855                     Author: [% helpers.get_li_attr(
3856                         "author", "", entry.lineitem.attributes
3857                     ) %]
3858                 [% END %]
3859                 [% IF entry.purchase_order %]
3860                     (PO: [% entry.purchase_order.name %])
3861                 [% END %]<br />
3862                 Invoice item count: [% entry.inv_item_count %]
3863                 [% IF entry.phys_item_count %]
3864                     / Physical item count: [% entry.phys_item_count %]
3865                 [% END %]
3866                 <br />
3867                 [% IF entry.cost_billed %]
3868                     Cost billed: [% entry.cost_billed %]
3869                     [% IF entry.billed_per_item %](per item)[% END %]
3870                     <br />
3871                 [% END %]
3872                 [% IF entry.actual_cost %]
3873                     Actual cost: [% entry.actual_cost %]<br />
3874                 [% END %]
3875                 [% IF entry.amount_paid %]
3876                     Amount paid: [% entry.amount_paid %]<br />
3877                 [% END %]
3878                 [% IF entry.note %]Note: [% entry.note %][% END %]
3879             </li>
3880         [% END %]
3881         [% FOR item IN invoice.items %]
3882             <li>
3883                 [% IF item.inv_item_type %]
3884                     Item Type: [% item.inv_item_type %]<br />
3885                 [% END %]
3886                 [% IF item.title %]Title/Description:
3887                     [% item.title %]<br />
3888                 [% END %]
3889                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3890                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3891                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3892                 [% IF item.cost_billed %]
3893                     Cost billed: [% item.cost_billed %]<br />
3894                 [% END %]
3895                 [% IF item.actual_cost %]
3896                     Actual cost: [% item.actual_cost %]<br />
3897                 [% END %]
3898                 [% IF item.amount_paid %]
3899                     Amount paid: [% item.amount_paid %]<br />
3900                 [% END %]
3901             </li>
3902         [% END %]
3903     </ul>
3904 </div>
3905 [% END %]
3906 $$
3907 );
3908
3909 INSERT INTO action_trigger.environment (event_def, path) VALUES
3910     (22, 'provider'),
3911     (22, 'provider.addresses'),
3912     (22, 'shipper'),
3913     (22, 'shipper.addresses'),
3914     (22, 'receiver'),
3915     (22, 'entries'),
3916     (22, 'entries.purchase_order'),
3917     (22, 'entries.lineitem'),
3918     (22, 'entries.lineitem.attributes'),
3919     (22, 'items')
3920 ;
3921
3922 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3923   (23, 'provider.edi_default');
3924
3925 INSERT INTO action_trigger.validator (module, description) 
3926     VALUES (
3927         'Acq::PurchaseOrderEDIRequired',
3928         oils_i18n_gettext(
3929             'Acq::PurchaseOrderEDIRequired',
3930             'Purchase order is delivered via EDI',
3931             'atval',
3932             'description'
3933         )
3934     );
3935
3936 INSERT INTO action_trigger.reactor (module, description)
3937     VALUES (
3938         'GeneratePurchaseOrderJEDI',
3939         oils_i18n_gettext(
3940             'GeneratePurchaseOrderJEDI',
3941             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
3942             'atreact',
3943             'description'
3944         )
3945     );
3946
3947 UPDATE action_trigger.hook 
3948     SET 
3949         key = 'acqpo.activated', 
3950         passive = FALSE,
3951         description = oils_i18n_gettext(
3952             'acqpo.activated',
3953             'Purchase order was activated',
3954             'ath',
3955             'description'
3956         )
3957     WHERE key = 'format.po.jedi';
3958
3959 -- We just changed a key in action_trigger.hook.  Now redirect any
3960 -- child rows to point to the new key.  (There probably aren't any;
3961 -- this is just a precaution against possible local modifications.)
3962
3963 UPDATE action_trigger.event_definition
3964 SET hook = 'acqpo.activated'
3965 WHERE hook = 'format.po.jedi';
3966
3967 INSERT INTO action_trigger.reactor (module, description) VALUES (
3968     'AstCall', 'Possibly place a phone call with Asterisk'
3969 );
3970
3971 INSERT INTO
3972     action_trigger.event_definition (
3973         id, active, owner, name, hook, validator, reactor,
3974         cleanup_success, cleanup_failure, delay, delay_field, group_field,
3975         max_delay, granularity, usr_field, opt_in_setting, template
3976     ) VALUES (
3977         24,
3978         FALSE,
3979         1,
3980         'Telephone Overdue Notice',
3981         'checkout.due', 'NOOP_True', 'AstCall',
3982         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
3983         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
3984         $$
3985 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
3986 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
3987 Channel: [% channel_prefix %]/[% country %][% phone %]
3988 Context: overdue-test
3989 MaxRetries: 1
3990 RetryTime: 60
3991 WaitTime: 30
3992 Extension: 10
3993 Archive: 1
3994 Set: eg_user_id=[% target.0.usr.id %]
3995 Set: items=[% target.size %]
3996 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
3997 $$
3998     );
3999
4000 INSERT INTO
4001     action_trigger.environment (id, event_def, path)
4002     VALUES
4003         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
4004         (DEFAULT, 24, 'usr')
4005     ;
4006
4007 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4008         'circ.format.history.email',
4009         'circ', 
4010         oils_i18n_gettext(
4011             'circ.format.history.email',
4012             'An email has been requested for a circ history.',
4013             'ath',
4014             'description'
4015         ), 
4016         FALSE
4017     )
4018     ,(
4019         'circ.format.history.print',
4020         'circ', 
4021         oils_i18n_gettext(
4022             'circ.format.history.print',
4023             'A circ history needs to be formatted for printing.',
4024             'ath',
4025             'description'
4026         ), 
4027         FALSE
4028     )
4029     ,(
4030         'ahr.format.history.email',
4031         'ahr', 
4032         oils_i18n_gettext(
4033             'ahr.format.history.email',
4034             'An email has been requested for a hold request history.',
4035             'ath',
4036             'description'
4037         ), 
4038         FALSE
4039     )
4040     ,(
4041         'ahr.format.history.print',
4042         'ahr', 
4043         oils_i18n_gettext(
4044             'ahr.format.history.print',
4045             'A hold request history needs to be formatted for printing.',
4046             'ath',
4047             'description'
4048         ), 
4049         FALSE
4050     )
4051
4052 ;
4053
4054 INSERT INTO action_trigger.event_definition (
4055         id,
4056         active,
4057         owner,
4058         name,
4059         hook,
4060         validator,
4061         reactor,
4062         group_field,
4063         granularity,
4064         template
4065     ) VALUES (
4066         25,
4067         TRUE,
4068         1,
4069         'circ.history.email',
4070         'circ.format.history.email',
4071         'NOOP_True',
4072         'SendEmail',
4073         'usr',
4074         NULL,
4075 $$
4076 [%- USE date -%]
4077 [%- SET user = target.0.usr -%]
4078 To: [%- params.recipient_email || user.email %]
4079 From: [%- params.sender_email || default_sender %]
4080 Subject: Circulation History
4081
4082     [% FOR circ IN target %]
4083             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4084             Barcode: [% circ.target_copy.barcode %]
4085             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
4086             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
4087             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
4088     [% END %]
4089 $$
4090     )
4091     ,(
4092         26,
4093         TRUE,
4094         1,
4095         'circ.history.print',
4096         'circ.format.history.print',
4097         'NOOP_True',
4098         'ProcessTemplate',
4099         'usr',
4100         'print-on-demand',
4101 $$
4102 [%- USE date -%]
4103 <div>
4104     <style> li { padding: 8px; margin 5px; }</style>
4105     <div>[% date.format %]</div>
4106     <br/>
4107
4108     [% user.family_name %], [% user.first_given_name %]
4109     <ol>
4110     [% FOR circ IN target %]
4111         <li>
4112             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
4113             <div>Barcode: [% circ.target_copy.barcode %]</div>
4114             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
4115             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
4116             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
4117         </li>
4118     [% END %]
4119     </ol>
4120 </div>
4121 $$
4122     )
4123     ,(
4124         27,
4125         TRUE,
4126         1,
4127         'ahr.history.email',
4128         'ahr.format.history.email',
4129         'NOOP_True',
4130         'SendEmail',
4131         'usr',
4132         NULL,
4133 $$
4134 [%- USE date -%]
4135 [%- SET user = target.0.usr -%]
4136 To: [%- params.recipient_email || user.email %]
4137 From: [%- params.sender_email || default_sender %]
4138 Subject: Hold Request History
4139
4140     [% FOR hold IN target %]
4141             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
4142             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
4143             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
4144     [% END %]
4145 $$
4146     )
4147     ,(
4148         28,
4149         TRUE,
4150         1,
4151         'ahr.history.print',
4152         'ahr.format.history.print',
4153         'NOOP_True',
4154         'ProcessTemplate',
4155         'usr',
4156         'print-on-demand',
4157 $$
4158 [%- USE date -%]
4159 <div>
4160     <style> li { padding: 8px; margin 5px; }</style>
4161     <div>[% date.format %]</div>
4162     <br/>
4163
4164     [% user.family_name %], [% user.first_given_name %]
4165     <ol>
4166     [% FOR hold IN target %]
4167         <li>
4168             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
4169             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
4170             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
4171         </li>
4172     [% END %]
4173     </ol>
4174 </div>
4175 $$
4176     )
4177
4178 ;
4179
4180 INSERT INTO action_trigger.environment (
4181         event_def,
4182         path
4183     ) VALUES 
4184          ( 25, 'target_copy')
4185         ,( 25, 'usr' )
4186         ,( 26, 'target_copy' )
4187         ,( 26, 'usr' )
4188         ,( 27, 'current_copy' )
4189         ,( 27, 'usr' )
4190         ,( 28, 'current_copy' )
4191         ,( 28, 'usr' )
4192 ;
4193
4194 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4195         'money.format.payment_receipt.email',
4196         'mp', 
4197         oils_i18n_gettext(
4198             'money.format.payment_receipt.email',
4199             'An email has been requested for a payment receipt.',
4200             'ath',
4201             'description'
4202         ), 
4203         FALSE
4204     )
4205     ,(
4206         'money.format.payment_receipt.print',
4207         'mp', 
4208         oils_i18n_gettext(
4209             'money.format.payment_receipt.print',
4210             'A payment receipt needs to be formatted for printing.',
4211             'ath',
4212             'description'
4213         ), 
4214         FALSE
4215     )
4216 ;
4217
4218 INSERT INTO action_trigger.event_definition (
4219         id,
4220         active,
4221         owner,
4222         name,
4223         hook,
4224         validator,
4225         reactor,
4226         group_field,
4227         granularity,
4228         template
4229     ) VALUES (
4230         29,
4231         TRUE,
4232         1,
4233         'money.payment_receipt.email',
4234         'money.format.payment_receipt.email',
4235         'NOOP_True',
4236         'SendEmail',
4237         'xact.usr',
4238         NULL,
4239 $$
4240 [%- USE date -%]
4241 [%- SET user = target.0.xact.usr -%]
4242 To: [%- params.recipient_email || user.email %]
4243 From: [%- params.sender_email || default_sender %]
4244 Subject: Payment Receipt
4245
4246 [% date.format -%]
4247 [%- SET xact_mp_hash = {} -%]
4248 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
4249     [%- SET xact_id = mp.xact.id -%]
4250     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
4251     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
4252 [%- END -%]
4253 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
4254     [%- SET xact = xact_mp_hash.$xact_id.xact %]
4255 Transaction ID: [% xact_id %]
4256     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4257     [% ELSE %]Miscellaneous
4258     [% END %]
4259     Line item billings:
4260         [%- SET mb_type_hash = {} -%]
4261         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
4262             [%- IF mb.voided == 'f' -%]
4263                 [%- SET mb_type = mb.btype.id -%]
4264                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
4265                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
4266                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
4267                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
4268                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
4269             [%- END -%]
4270         [%- END -%]
4271         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
4272             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
4273                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4274                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4275             [%- ELSE -%][%# all other billings show individually %]
4276                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
4277                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4278                 [% END %]
4279             [% END %]
4280         [% END %]
4281     Line item payments:
4282         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4283             Payment ID: [% mp.id %]
4284                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4285                     [% CASE "cash_payment" %]cash
4286                     [% CASE "check_payment" %]check
4287                     [% CASE "credit_card_payment" %]credit card (
4288                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4289                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4290                         [% cc_chunks.last -%]
4291                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4292                     )
4293                     [% CASE "credit_payment" %]credit
4294                     [% CASE "forgive_payment" %]forgiveness
4295                     [% CASE "goods_payment" %]goods
4296                     [% CASE "work_payment" %]work
4297                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
4298         [% END %]
4299 [% END %]
4300 $$
4301     )
4302     ,(
4303         30,
4304         TRUE,
4305         1,
4306         'money.payment_receipt.print',
4307         'money.format.payment_receipt.print',
4308         'NOOP_True',
4309         'ProcessTemplate',
4310         'xact.usr',
4311         'print-on-demand',
4312 $$
4313 [%- USE date -%][%- SET user = target.0.xact.usr -%]
4314 <div style="li { padding: 8px; margin 5px; }">
4315     <div>[% date.format %]</div><br/>
4316     <ol>
4317     [% SET xact_mp_hash = {} %]
4318     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
4319         [% SET xact_id = mp.xact.id %]
4320         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
4321         [% xact_mp_hash.$xact_id.payments.push(mp) %]
4322     [% END %]
4323     [% FOR xact_id IN xact_mp_hash.keys.sort %]
4324         [% SET xact = xact_mp_hash.$xact_id.xact %]
4325         <li>Transaction ID: [% xact_id %]
4326             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4327             [% ELSE %]Miscellaneous
4328             [% END %]
4329             Line item billings:<ol>
4330                 [% SET mb_type_hash = {} %]
4331                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
4332                     [% IF mb.voided == 'f' %]
4333                         [% SET mb_type = mb.btype.id %]
4334                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
4335                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
4336                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
4337                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
4338                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
4339                     [% END %]
4340                 [% END %]
4341                 [% FOR mb_type IN mb_type_hash.keys.sort %]
4342                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
4343                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4344                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4345                     [% ELSE %][%# all other billings show individually %]
4346                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
4347                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4348                         [% END %]
4349                     [% END %]</li>
4350                 [% END %]
4351             </ol>
4352             Line item payments:<ol>
4353                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4354                     <li>Payment ID: [% mp.id %]
4355                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4356                             [% CASE "cash_payment" %]cash
4357                             [% CASE "check_payment" %]check
4358                             [% CASE "credit_card_payment" %]credit card (
4359                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4360                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4361                                 [% cc_chunks.last -%]
4362                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4363                             )
4364                             [% CASE "credit_payment" %]credit
4365                             [% CASE "forgive_payment" %]forgiveness
4366                             [% CASE "goods_payment" %]goods
4367                             [% CASE "work_payment" %]work
4368                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
4369                     </li>
4370                 [% END %]
4371             </ol>
4372         </li>
4373     [% END %]
4374     </ol>
4375 </div>
4376 $$
4377     )
4378 ;
4379
4380 INSERT INTO action_trigger.environment (
4381         event_def,
4382         path
4383     ) VALUES -- for fleshing mp objects
4384          ( 29, 'xact')
4385         ,( 29, 'xact.usr')
4386         ,( 29, 'xact.grocery' )
4387         ,( 29, 'xact.circulation' )
4388         ,( 29, 'xact.summary' )
4389         ,( 30, 'xact')
4390         ,( 30, 'xact.usr')
4391         ,( 30, 'xact.grocery' )
4392         ,( 30, 'xact.circulation' )
4393         ,( 30, 'xact.summary' )
4394 ;
4395
4396 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
4397     'DeleteTempBiblioBucket',
4398     oils_i18n_gettext(
4399         'DeleteTempBiblioBucket',
4400         'Deletes a cbreb object used as a target if it has a btype of "temp"',
4401         'atclean',
4402         'description'
4403     )
4404 );
4405
4406 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4407         'biblio.format.record_entry.email',
4408         'cbreb', 
4409         oils_i18n_gettext(
4410             'biblio.format.record_entry.email',
4411             'An email has been requested for one or more biblio record entries.',
4412             'ath',
4413             'description'
4414         ), 
4415         FALSE
4416     )
4417     ,(
4418         'biblio.format.record_entry.print',
4419         'cbreb', 
4420         oils_i18n_gettext(
4421             'biblio.format.record_entry.print',
4422             'One or more biblio record entries need to be formatted for printing.',
4423             'ath',
4424             'description'
4425         ), 
4426         FALSE
4427     )
4428 ;
4429
4430 INSERT INTO action_trigger.event_definition (
4431         id,
4432         active,
4433         owner,
4434         name,
4435         hook,
4436         validator,
4437         reactor,
4438         cleanup_success,
4439         cleanup_failure,
4440         group_field,
4441         granularity,
4442         template
4443     ) VALUES (
4444         31,
4445         TRUE,
4446         1,
4447         'biblio.record_entry.email',
4448         'biblio.format.record_entry.email',
4449         'NOOP_True',
4450         'SendEmail',
4451         'DeleteTempBiblioBucket',
4452         'DeleteTempBiblioBucket',
4453         'owner',
4454         NULL,
4455 $$
4456 [%- USE date -%]
4457 [%- SET user = target.0.owner -%]
4458 To: [%- params.recipient_email || user.email %]
4459 From: [%- params.sender_email || default_sender %]
4460 Subject: Bibliographic Records
4461
4462     [% FOR cbreb IN target %]
4463     [% FOR cbrebi IN cbreb.items %]
4464         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
4465         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
4466         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
4467         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4468
4469     [% END %]
4470     [% END %]
4471 $$
4472     )
4473     ,(
4474         32,
4475         TRUE,
4476         1,
4477         'biblio.record_entry.print',
4478         'biblio.format.record_entry.print',
4479         'NOOP_True',
4480         'ProcessTemplate',
4481         'DeleteTempBiblioBucket',
4482         'DeleteTempBiblioBucket',
4483         'owner',
4484         'print-on-demand',
4485 $$
4486 [%- USE date -%]
4487 <div>
4488     <style> li { padding: 8px; margin 5px; }</style>
4489     <ol>
4490     [% FOR cbreb IN target %]
4491     [% FOR cbrebi IN cbreb.items %]
4492         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
4493             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
4494             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
4495             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4496         </li>
4497     [% END %]
4498     [% END %]
4499     </ol>
4500 </div>
4501 $$
4502     )
4503 ;
4504
4505 INSERT INTO action_trigger.environment (
4506         event_def,
4507         path
4508     ) VALUES -- for fleshing cbreb objects
4509          ( 31, 'owner' )
4510         ,( 31, 'items' )
4511         ,( 31, 'items.target_biblio_record_entry' )
4512         ,( 31, 'items.target_biblio_record_entry.simple_record' )
4513         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
4514         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
4515         ,( 31, 'items.target_biblio_record_entry.notes' )
4516         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
4517         ,( 32, 'owner' )
4518         ,( 32, 'items' )
4519         ,( 32, 'items.target_biblio_record_entry' )
4520         ,( 32, 'items.target_biblio_record_entry.simple_record' )
4521         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
4522         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
4523         ,( 32, 'items.target_biblio_record_entry.notes' )
4524         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
4525 ;
4526
4527 INSERT INTO action_trigger.environment (
4528         event_def,
4529         path
4530     ) VALUES -- for fleshing mp objects
4531          ( 29, 'credit_card_payment')
4532         ,( 29, 'xact.billings')
4533         ,( 29, 'xact.billings.btype')
4534         ,( 30, 'credit_card_payment')
4535         ,( 30, 'xact.billings')
4536         ,( 30, 'xact.billings.btype')
4537 ;
4538
4539 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
4540     (   'circ.format.missing_pieces.slip.print',
4541         'circ', 
4542         oils_i18n_gettext(
4543             'circ.format.missing_pieces.slip.print',
4544             'A missing pieces slip needs to be formatted for printing.',
4545             'ath',
4546             'description'
4547         ), 
4548         FALSE
4549     )
4550     ,(  'circ.format.missing_pieces.letter.print',
4551         'circ', 
4552         oils_i18n_gettext(
4553             'circ.format.missing_pieces.letter.print',
4554             'A missing pieces patron letter needs to be formatted for printing.',
4555             'ath',
4556             'description'
4557         ), 
4558         FALSE
4559     )
4560 ;
4561
4562 INSERT INTO action_trigger.event_definition (
4563         id,
4564         active,
4565         owner,
4566         name,
4567         hook,
4568         validator,
4569         reactor,
4570         group_field,
4571         granularity,
4572         template
4573     ) VALUES (
4574         33,
4575         TRUE,
4576         1,
4577         'circ.missing_pieces.slip.print',
4578         'circ.format.missing_pieces.slip.print',
4579         'NOOP_True',
4580         'ProcessTemplate',
4581         'usr',
4582         'print-on-demand',
4583 $$
4584 [%- USE date -%]
4585 [%- SET user = target.0.usr -%]
4586 <div style="li { padding: 8px; margin 5px; }">
4587     <div>[% date.format %]</div><br/>
4588     Missing pieces for:
4589     <ol>
4590     [% FOR circ IN target %]
4591         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
4592             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4593         </li>
4594     [% END %]
4595     </ol>
4596 </div>
4597 $$
4598     )
4599     ,(
4600         34,
4601         TRUE,
4602         1,
4603         'circ.missing_pieces.letter.print',
4604         'circ.format.missing_pieces.letter.print',
4605         'NOOP_True',
4606         'ProcessTemplate',
4607         'usr',
4608         'print-on-demand',
4609 $$
4610 [%- USE date -%]
4611 [%- SET user = target.0.usr -%]
4612 [% date.format %]
4613 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
4614
4615 We are missing pieces for the following returned items:
4616 [% FOR circ IN target %]
4617 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
4618 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4619 [% END %]
4620
4621 Please return these pieces as soon as possible.
4622
4623 Thanks!
4624
4625 Library Staff
4626 $$
4627     )
4628 ;
4629
4630 INSERT INTO action_trigger.environment (
4631         event_def,
4632         path
4633     ) VALUES -- for fleshing circ objects
4634          ( 33, 'usr')
4635         ,( 33, 'target_copy')
4636         ,( 33, 'target_copy.circ_lib')
4637         ,( 33, 'target_copy.circ_lib.mailing_address')
4638         ,( 33, 'target_copy.circ_lib.billing_address')
4639         ,( 33, 'target_copy.call_number')
4640         ,( 33, 'target_copy.call_number.owning_lib')
4641         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
4642         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
4643         ,( 33, 'circ_lib')
4644         ,( 33, 'circ_lib.mailing_address')
4645         ,( 33, 'circ_lib.billing_address')
4646         ,( 34, 'usr')
4647         ,( 34, 'target_copy')
4648         ,( 34, 'target_copy.circ_lib')
4649         ,( 34, 'target_copy.circ_lib.mailing_address')
4650         ,( 34, 'target_copy.circ_lib.billing_address')
4651         ,( 34, 'target_copy.call_number')
4652         ,( 34, 'target_copy.call_number.owning_lib')
4653         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
4654         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
4655         ,( 34, 'circ_lib')
4656         ,( 34, 'circ_lib.mailing_address')
4657         ,( 34, 'circ_lib.billing_address')
4658 ;
4659
4660 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
4661     VALUES (   
4662         'ahr.format.pull_list',
4663         'ahr', 
4664         oils_i18n_gettext(
4665             'ahr.format.pull_list',
4666             'Format holds pull list for printing',
4667             'ath',
4668             'description'
4669         ), 
4670         FALSE
4671     );
4672
4673 INSERT INTO action_trigger.event_definition (
4674         id,
4675         active,
4676         owner,
4677         name,
4678         hook,
4679         validator,
4680         reactor,
4681         group_field,
4682         granularity,
4683         template
4684     ) VALUES (
4685         35,
4686         TRUE,
4687         1,
4688         'Holds Pull List',
4689         'ahr.format.pull_list',
4690         'NOOP_True',
4691         'ProcessTemplate',
4692         'pickup_lib',
4693         'print-on-demand',
4694 $$
4695 [%- USE date -%]
4696 <style>
4697     table { border-collapse: collapse; } 
4698     td { padding: 5px; border-bottom: 1px solid #888; } 
4699     th { font-weight: bold; }
4700 </style>
4701 [% 
4702     # Sort the holds into copy-location buckets
4703     # In the main print loop, sort each bucket by callnumber before printing
4704     SET holds_list = [];
4705     SET loc_data = [];
4706     SET current_location = target.0.current_copy.location.id;
4707     FOR hold IN target;
4708         IF current_location != hold.current_copy.location.id;
4709             SET current_location = hold.current_copy.location.id;
4710             holds_list.push(loc_data);
4711             SET loc_data = [];
4712         END;
4713         SET hold_data = {
4714             'hold' => hold,
4715             'callnumber' => hold.current_copy.call_number.label
4716         };
4717         loc_data.push(hold_data);
4718     END;
4719     holds_list.push(loc_data)
4720 %]
4721 <table>
4722     <thead>
4723         <tr>
4724             <th>Title</th>
4725             <th>Author</th>
4726             <th>Shelving Location</th>
4727             <th>Call Number</th>
4728             <th>Barcode</th>
4729             <th>Patron</th>
4730         </tr>
4731     </thead>
4732     <tbody>
4733     [% FOR loc_data IN holds_list  %]
4734         [% FOR hold_data IN loc_data.sort('callnumber') %]
4735             [% 
4736                 SET hold = hold_data.hold;
4737                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
4738             %]
4739             <tr>
4740                 <td>[% copy_data.title | truncate %]</td>
4741                 <td>[% copy_data.author | truncate %]</td>
4742                 <td>[% hold.current_copy.location.name %]</td>
4743                 <td>[% hold.current_copy.call_number.label %]</td>
4744                 <td>[% hold.current_copy.barcode %]</td>
4745                 <td>[% hold.usr.card.barcode %]</td>
4746             </tr>
4747         [% END %]
4748     [% END %]
4749     <tbody>
4750 </table>
4751 $$
4752 );
4753
4754 INSERT INTO action_trigger.environment (
4755         event_def,
4756         path
4757     ) VALUES
4758         (35, 'current_copy.location'),
4759         (35, 'current_copy.call_number'),
4760         (35, 'usr.card'),
4761         (35, 'pickup_lib')
4762 ;
4763
4764 INSERT INTO action_trigger.validator (module, description) VALUES ( 
4765     'HoldIsCancelled', 
4766     oils_i18n_gettext( 
4767         'HoldIsCancelled', 
4768         'Check whether a hold request is cancelled.', 
4769         'atval', 
4770         'description' 
4771     ) 
4772 );
4773
4774 -- Create the query schema, and the tables and views therein
4775
4776 DROP SCHEMA IF EXISTS sql CASCADE;
4777 DROP SCHEMA IF EXISTS query CASCADE;
4778
4779 CREATE SCHEMA query;
4780
4781 CREATE TABLE query.datatype (
4782         id              SERIAL            PRIMARY KEY,
4783         datatype_name   TEXT              NOT NULL UNIQUE,
4784         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
4785         is_composite    BOOL              NOT NULL DEFAULT FALSE,
4786         CONSTRAINT qdt_comp_not_num CHECK
4787         ( is_numeric IS FALSE OR is_composite IS FALSE )
4788 );
4789
4790 -- Define the most common datatypes in query.datatype.  Note that none of
4791 -- these stock datatypes specifies a width or precision.
4792
4793 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
4794 -- room for more stock datatypes if we ever want to add them.
4795
4796 SELECT setval( 'query.datatype_id_seq', 1000 );
4797
4798 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4799   VALUES (1, 'SMALLINT', true);
4800  
4801 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4802   VALUES (2, 'INTEGER', true);
4803  
4804 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4805   VALUES (3, 'BIGINT', true);
4806  
4807 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4808   VALUES (4, 'DECIMAL', true);
4809  
4810 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4811   VALUES (5, 'NUMERIC', true);
4812  
4813 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4814   VALUES (6, 'REAL', true);
4815  
4816 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4817   VALUES (7, 'DOUBLE PRECISION', true);
4818  
4819 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4820   VALUES (8, 'SERIAL', true);
4821  
4822 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4823   VALUES (9, 'BIGSERIAL', true);
4824  
4825 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4826   VALUES (10, 'MONEY', false);
4827  
4828 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4829   VALUES (11, 'VARCHAR', false);
4830  
4831 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4832   VALUES (12, 'CHAR', false);
4833  
4834 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4835   VALUES (13, 'TEXT', false);
4836  
4837 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4838   VALUES (14, '"char"', false);
4839  
4840 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4841   VALUES (15, 'NAME', false);
4842  
4843 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4844   VALUES (16, 'BYTEA', false);
4845  
4846 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4847   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
4848  
4849 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4850   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
4851  
4852 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4853   VALUES (19, 'DATE', false);
4854  
4855 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4856   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
4857  
4858 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4859   VALUES (21, 'TIME WITH TIME ZONE', false);
4860  
4861 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4862   VALUES (22, 'INTERVAL', false);
4863  
4864 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4865   VALUES (23, 'BOOLEAN', false);
4866  
4867 CREATE TABLE query.subfield (
4868         id              SERIAL            PRIMARY KEY,
4869         composite_type  INT               NOT NULL
4870                                           REFERENCES query.datatype(id)
4871                                           ON DELETE CASCADE
4872                                           DEFERRABLE INITIALLY DEFERRED,
4873         seq_no          INT               NOT NULL
4874                                           CONSTRAINT qsf_pos_seq_no
4875                                           CHECK( seq_no > 0 ),
4876         subfield_type   INT               NOT NULL
4877                                           REFERENCES query.datatype(id)
4878                                           DEFERRABLE INITIALLY DEFERRED,
4879         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
4880 );
4881
4882 CREATE TABLE query.function_sig (
4883         id              SERIAL            PRIMARY KEY,
4884         function_name   TEXT              NOT NULL,
4885         return_type     INT               REFERENCES query.datatype(id)
4886                                           DEFERRABLE INITIALLY DEFERRED,
4887         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
4888         CONSTRAINT qfd_rtn_or_aggr CHECK
4889         ( return_type IS NULL OR is_aggregate = FALSE )
4890 );
4891
4892 CREATE INDEX query_function_sig_name_idx 
4893         ON query.function_sig (function_name);
4894
4895 CREATE TABLE query.function_param_def (
4896         id              SERIAL            PRIMARY KEY,
4897         function_id     INT               NOT NULL
4898                                           REFERENCES query.function_sig( id )
4899                                           ON DELETE CASCADE
4900                                           DEFERRABLE INITIALLY DEFERRED,
4901         seq_no          INT               NOT NULL
4902                                           CONSTRAINT qfpd_pos_seq_no CHECK
4903                                           ( seq_no > 0 ),
4904         datatype        INT               NOT NULL
4905                                           REFERENCES query.datatype( id )
4906                                           DEFERRABLE INITIALLY DEFERRED,
4907         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
4908 );
4909
4910 CREATE TABLE  query.stored_query (
4911         id            SERIAL         PRIMARY KEY,
4912         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
4913                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
4914         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
4915         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
4916         from_clause   INT            , --REFERENCES query.from_clause
4917                                      --DEFERRABLE INITIALLY DEFERRED,
4918         where_clause  INT            , --REFERENCES query.expression
4919                                      --DEFERRABLE INITIALLY DEFERRED,
4920         having_clause INT            , --REFERENCES query.expression
4921                                      --DEFERRABLE INITIALLY DEFERRED
4922         limit_count   INT            , --REFERENCES query.expression( id )
4923                                      --DEFERRABLE INITIALLY DEFERRED,
4924         offset_count  INT            --REFERENCES query.expression( id )
4925                                      --DEFERRABLE INITIALLY DEFERRED
4926 );
4927
4928 -- (Foreign keys to be defined later after other tables are created)
4929
4930 CREATE TABLE query.query_sequence (
4931         id              SERIAL            PRIMARY KEY,
4932         parent_query    INT               NOT NULL
4933                                           REFERENCES query.stored_query
4934                                                                           ON DELETE CASCADE
4935                                                                           DEFERRABLE INITIALLY DEFERRED,
4936         seq_no          INT               NOT NULL,
4937         child_query     INT               NOT NULL
4938                                           REFERENCES query.stored_query
4939                                                                           ON DELETE CASCADE
4940                                                                           DEFERRABLE INITIALLY DEFERRED,
4941         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
4942 );
4943
4944 CREATE TABLE query.bind_variable (
4945         name          TEXT             PRIMARY KEY,
4946         type          TEXT             NOT NULL
4947                                            CONSTRAINT bind_variable_type CHECK
4948                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
4949         description   TEXT             NOT NULL,
4950         default_value TEXT,            -- to be encoded in JSON
4951         label         TEXT             NOT NULL
4952 );
4953
4954 CREATE TABLE query.expression (
4955         id            SERIAL        PRIMARY KEY,
4956         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
4957                                     ( type IN (
4958                                     'xbet',    -- between
4959                                     'xbind',   -- bind variable
4960                                     'xbool',   -- boolean
4961                                     'xcase',   -- case
4962                                     'xcast',   -- cast
4963                                     'xcol',    -- column
4964                                     'xex',     -- exists
4965                                     'xfunc',   -- function
4966                                     'xin',     -- in
4967                                     'xisnull', -- is null
4968                                     'xnull',   -- null
4969                                     'xnum',    -- number
4970                                     'xop',     -- operator
4971                                     'xser',    -- series
4972                                     'xstr',    -- string
4973                                     'xsubq'    -- subquery
4974                                                                 ) ),
4975         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
4976         parent_expr   INT           REFERENCES query.expression
4977                                     ON DELETE CASCADE
4978                                     DEFERRABLE INITIALLY DEFERRED,
4979         seq_no        INT           NOT NULL DEFAULT 1,
4980         literal       TEXT,
4981         table_alias   TEXT,
4982         column_name   TEXT,
4983         left_operand  INT           REFERENCES query.expression
4984                                     DEFERRABLE INITIALLY DEFERRED,
4985         operator      TEXT,
4986         right_operand INT           REFERENCES query.expression
4987                                     DEFERRABLE INITIALLY DEFERRED,
4988         function_id   INT           REFERENCES query.function_sig
4989                                     DEFERRABLE INITIALLY DEFERRED,
4990         subquery      INT           REFERENCES query.stored_query
4991                                     DEFERRABLE INITIALLY DEFERRED,
4992         cast_type     INT           REFERENCES query.datatype
4993                                     DEFERRABLE INITIALLY DEFERRED,
4994         negate        BOOL          NOT NULL DEFAULT FALSE,
4995         bind_variable TEXT          REFERENCES query.bind_variable
4996                                         DEFERRABLE INITIALLY DEFERRED
4997 );
4998
4999 CREATE UNIQUE INDEX query_expr_parent_seq
5000         ON query.expression( parent_expr, seq_no )
5001         WHERE parent_expr IS NOT NULL;
5002
5003 -- Due to some circular references, the following foreign key definitions
5004 -- had to be deferred until query.expression existed:
5005
5006 ALTER TABLE query.stored_query
5007         ADD FOREIGN KEY ( where_clause )
5008         REFERENCES query.expression( id )
5009         DEFERRABLE INITIALLY DEFERRED;
5010
5011 ALTER TABLE query.stored_query
5012         ADD FOREIGN KEY ( having_clause )
5013         REFERENCES query.expression( id )
5014         DEFERRABLE INITIALLY DEFERRED;
5015
5016 ALTER TABLE query.stored_query
5017     ADD FOREIGN KEY ( limit_count )
5018     REFERENCES query.expression( id )
5019     DEFERRABLE INITIALLY DEFERRED;
5020
5021 ALTER TABLE query.stored_query
5022     ADD FOREIGN KEY ( offset_count )
5023     REFERENCES query.expression( id )
5024     DEFERRABLE INITIALLY DEFERRED;
5025
5026 CREATE TABLE query.case_branch (
5027         id            SERIAL        PRIMARY KEY,
5028         parent_expr   INT           NOT NULL REFERENCES query.expression
5029                                     ON DELETE CASCADE
5030                                     DEFERRABLE INITIALLY DEFERRED,
5031         seq_no        INT           NOT NULL,
5032         condition     INT           REFERENCES query.expression
5033                                     DEFERRABLE INITIALLY DEFERRED,
5034         result        INT           NOT NULL REFERENCES query.expression
5035                                     DEFERRABLE INITIALLY DEFERRED,
5036         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
5037 );
5038
5039 CREATE TABLE query.from_relation (
5040         id               SERIAL        PRIMARY KEY,
5041         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
5042                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
5043         table_name       TEXT,
5044         class_name       TEXT,
5045         subquery         INT           REFERENCES query.stored_query,
5046         function_call    INT           REFERENCES query.expression,
5047         table_alias      TEXT,
5048         parent_relation  INT           REFERENCES query.from_relation
5049                                        ON DELETE CASCADE
5050                                        DEFERRABLE INITIALLY DEFERRED,
5051         seq_no           INT           NOT NULL DEFAULT 1,
5052         join_type        TEXT          CONSTRAINT good_join_type CHECK (
5053                                            join_type IS NULL OR join_type IN
5054                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
5055                                        ),
5056         on_clause        INT           REFERENCES query.expression
5057                                        DEFERRABLE INITIALLY DEFERRED,
5058         CONSTRAINT join_or_core CHECK (
5059         ( parent_relation IS NULL AND join_type IS NULL
5060           AND on_clause IS NULL )
5061         OR
5062         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
5063           AND on_clause IS NOT NULL )
5064         )
5065 );
5066
5067 CREATE UNIQUE INDEX from_parent_seq
5068         ON query.from_relation( parent_relation, seq_no )
5069         WHERE parent_relation IS NOT NULL;
5070
5071 -- The following foreign key had to be deferred until
5072 -- query.from_relation existed
5073
5074 ALTER TABLE query.stored_query
5075         ADD FOREIGN KEY (from_clause)
5076         REFERENCES query.from_relation
5077         DEFERRABLE INITIALLY DEFERRED;
5078
5079 CREATE TABLE query.record_column (
5080         id            SERIAL            PRIMARY KEY,
5081         from_relation INT               NOT NULL REFERENCES query.from_relation
5082                                         ON DELETE CASCADE
5083                                         DEFERRABLE INITIALLY DEFERRED,
5084         seq_no        INT               NOT NULL,
5085         column_name   TEXT              NOT NULL,
5086         column_type   INT               NOT NULL REFERENCES query.datatype
5087                                         ON DELETE CASCADE
5088                                                                         DEFERRABLE INITIALLY DEFERRED,
5089         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
5090 );
5091
5092 CREATE TABLE query.select_item (
5093         id               SERIAL         PRIMARY KEY,
5094         stored_query     INT            NOT NULL REFERENCES query.stored_query
5095                                         ON DELETE CASCADE
5096                                         DEFERRABLE INITIALLY DEFERRED,
5097         seq_no           INT            NOT NULL,
5098         expression       INT            NOT NULL REFERENCES query.expression
5099                                         DEFERRABLE INITIALLY DEFERRED,
5100         column_alias     TEXT,
5101         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
5102         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
5103 );
5104
5105 CREATE TABLE query.order_by_item (
5106         id               SERIAL         PRIMARY KEY,
5107         stored_query     INT            NOT NULL REFERENCES query.stored_query
5108                                         ON DELETE CASCADE
5109                                         DEFERRABLE INITIALLY DEFERRED,
5110         seq_no           INT            NOT NULL,
5111         expression       INT            NOT NULL REFERENCES query.expression
5112                                         ON DELETE CASCADE
5113                                         DEFERRABLE INITIALLY DEFERRED,
5114         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
5115 );
5116
5117 ------------------------------------------------------------
5118 -- Create updatable views for different kinds of expressions
5119 ------------------------------------------------------------
5120
5121 -- Create updatable view for BETWEEN expressions
5122
5123 CREATE OR REPLACE VIEW query.expr_xbet AS
5124     SELECT
5125                 id,
5126                 parenthesize,
5127                 parent_expr,
5128                 seq_no,
5129                 left_operand,
5130                 negate
5131     FROM
5132         query.expression
5133     WHERE
5134         type = 'xbet';
5135
5136 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
5137     ON INSERT TO query.expr_xbet
5138     DO INSTEAD
5139     INSERT INTO query.expression (
5140                 id,
5141                 type,
5142                 parenthesize,
5143                 parent_expr,
5144                 seq_no,
5145                 left_operand,
5146                 negate
5147     ) VALUES (
5148         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5149         'xbet',
5150         COALESCE(NEW.parenthesize, FALSE),
5151         NEW.parent_expr,
5152         COALESCE(NEW.seq_no, 1),
5153                 NEW.left_operand,
5154                 COALESCE(NEW.negate, false)
5155     );
5156
5157 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
5158     ON UPDATE TO query.expr_xbet
5159     DO INSTEAD
5160     UPDATE query.expression SET
5161         id = NEW.id,
5162         parenthesize = NEW.parenthesize,
5163         parent_expr = NEW.parent_expr,
5164         seq_no = NEW.seq_no,
5165                 left_operand = NEW.left_operand,
5166                 negate = NEW.negate
5167     WHERE
5168         id = OLD.id;
5169
5170 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
5171     ON DELETE TO query.expr_xbet
5172     DO INSTEAD
5173     DELETE FROM query.expression WHERE id = OLD.id;
5174
5175 -- Create updatable view for bind variable expressions
5176
5177 CREATE OR REPLACE VIEW query.expr_xbind AS
5178     SELECT
5179                 id,
5180                 parenthesize,
5181                 parent_expr,
5182                 seq_no,
5183                 bind_variable
5184     FROM
5185         query.expression
5186     WHERE
5187         type = 'xbind';
5188
5189 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
5190     ON INSERT TO query.expr_xbind
5191     DO INSTEAD
5192     INSERT INTO query.expression (
5193                 id,
5194                 type,
5195                 parenthesize,
5196                 parent_expr,
5197                 seq_no,
5198                 bind_variable
5199     ) VALUES (
5200         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5201         'xbind',
5202         COALESCE(NEW.parenthesize, FALSE),
5203         NEW.parent_expr,
5204         COALESCE(NEW.seq_no, 1),
5205                 NEW.bind_variable
5206     );
5207
5208 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
5209     ON UPDATE TO query.expr_xbind
5210     DO INSTEAD
5211     UPDATE query.expression SET
5212         id = NEW.id,
5213         parenthesize = NEW.parenthesize,
5214         parent_expr = NEW.parent_expr,
5215         seq_no = NEW.seq_no,
5216                 bind_variable = NEW.bind_variable
5217     WHERE
5218         id = OLD.id;
5219
5220 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
5221     ON DELETE TO query.expr_xbind
5222     DO INSTEAD
5223     DELETE FROM query.expression WHERE id = OLD.id;
5224
5225 -- Create updatable view for boolean expressions
5226
5227 CREATE OR REPLACE VIEW query.expr_xbool AS
5228     SELECT
5229                 id,
5230                 parenthesize,
5231                 parent_expr,
5232                 seq_no,
5233                 literal,
5234                 negate
5235     FROM
5236         query.expression
5237     WHERE
5238         type = 'xbool';
5239
5240 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
5241     ON INSERT TO query.expr_xbool
5242     DO INSTEAD
5243     INSERT INTO query.expression (
5244                 id,
5245                 type,
5246                 parenthesize,
5247                 parent_expr,
5248                 seq_no,
5249                 literal,
5250                 negate
5251     ) VALUES (
5252         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5253         'xbool',
5254         COALESCE(NEW.parenthesize, FALSE),
5255         NEW.parent_expr,
5256         COALESCE(NEW.seq_no, 1),
5257         NEW.literal,
5258                 COALESCE(NEW.negate, false)
5259     );
5260
5261 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
5262     ON UPDATE TO query.expr_xbool
5263     DO INSTEAD
5264     UPDATE query.expression SET
5265         id = NEW.id,
5266         parenthesize = NEW.parenthesize,
5267         parent_expr = NEW.parent_expr,
5268         seq_no = NEW.seq_no,
5269         literal = NEW.literal,
5270                 negate = NEW.negate
5271     WHERE
5272         id = OLD.id;
5273
5274 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
5275     ON DELETE TO query.expr_xbool
5276     DO INSTEAD
5277     DELETE FROM query.expression WHERE id = OLD.id;
5278
5279 -- Create updatable view for CASE expressions
5280
5281 CREATE OR REPLACE VIEW query.expr_xcase AS
5282     SELECT
5283                 id,
5284                 parenthesize,
5285                 parent_expr,
5286                 seq_no,
5287                 left_operand,
5288                 negate
5289     FROM
5290         query.expression
5291     WHERE
5292         type = 'xcase';
5293
5294 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
5295     ON INSERT TO query.expr_xcase
5296     DO INSTEAD
5297     INSERT INTO query.expression (
5298                 id,
5299                 type,
5300                 parenthesize,
5301                 parent_expr,
5302                 seq_no,
5303                 left_operand,
5304                 negate
5305     ) VALUES (
5306         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5307         'xcase',
5308         COALESCE(NEW.parenthesize, FALSE),
5309         NEW.parent_expr,
5310         COALESCE(NEW.seq_no, 1),
5311                 NEW.left_operand,
5312                 COALESCE(NEW.negate, false)
5313     );
5314
5315 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
5316     ON UPDATE TO query.expr_xcase
5317     DO INSTEAD
5318     UPDATE query.expression SET
5319         id = NEW.id,
5320         parenthesize = NEW.parenthesize,
5321         parent_expr = NEW.parent_expr,
5322         seq_no = NEW.seq_no,
5323                 left_operand = NEW.left_operand,
5324                 negate = NEW.negate
5325     WHERE
5326         id = OLD.id;
5327
5328 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
5329     ON DELETE TO query.expr_xcase
5330     DO INSTEAD
5331     DELETE FROM query.expression WHERE id = OLD.id;
5332
5333 -- Create updatable view for cast expressions
5334
5335 CREATE OR REPLACE VIEW query.expr_xcast AS
5336     SELECT
5337                 id,
5338                 parenthesize,
5339                 parent_expr,
5340                 seq_no,
5341                 left_operand,
5342                 cast_type,
5343                 negate
5344     FROM
5345         query.expression
5346     WHERE
5347         type = 'xcast';
5348
5349 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
5350     ON INSERT TO query.expr_xcast
5351     DO INSTEAD
5352     INSERT INTO query.expression (
5353         id,
5354         type,
5355         parenthesize,
5356         parent_expr,
5357         seq_no,
5358         left_operand,
5359         cast_type,
5360         negate
5361     ) VALUES (
5362         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5363         'xcast',
5364         COALESCE(NEW.parenthesize, FALSE),
5365         NEW.parent_expr,
5366         COALESCE(NEW.seq_no, 1),
5367         NEW.left_operand,
5368         NEW.cast_type,
5369         COALESCE(NEW.negate, false)
5370     );
5371
5372 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
5373     ON UPDATE TO query.expr_xcast
5374     DO INSTEAD
5375     UPDATE query.expression SET
5376         id = NEW.id,
5377         parenthesize = NEW.parenthesize,
5378         parent_expr = NEW.parent_expr,
5379         seq_no = NEW.seq_no,
5380                 left_operand = NEW.left_operand,
5381                 cast_type = NEW.cast_type,
5382                 negate = NEW.negate
5383     WHERE
5384         id = OLD.id;
5385
5386 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
5387     ON DELETE TO query.expr_xcast
5388     DO INSTEAD
5389     DELETE FROM query.expression WHERE id = OLD.id;
5390
5391 -- Create updatable view for column expressions
5392
5393 CREATE OR REPLACE VIEW query.expr_xcol AS
5394     SELECT
5395                 id,
5396                 parenthesize,
5397                 parent_expr,
5398                 seq_no,
5399                 table_alias,
5400                 column_name,
5401                 negate
5402     FROM
5403         query.expression
5404     WHERE
5405         type = 'xcol';
5406
5407 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
5408     ON INSERT TO query.expr_xcol
5409     DO INSTEAD
5410     INSERT INTO query.expression (
5411                 id,
5412                 type,
5413                 parenthesize,
5414                 parent_expr,
5415                 seq_no,
5416                 table_alias,
5417                 column_name,
5418                 negate
5419     ) VALUES (
5420         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5421         'xcol',
5422         COALESCE(NEW.parenthesize, FALSE),
5423         NEW.parent_expr,
5424         COALESCE(NEW.seq_no, 1),
5425                 NEW.table_alias,
5426                 NEW.column_name,
5427                 COALESCE(NEW.negate, false)
5428     );
5429
5430 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
5431     ON UPDATE TO query.expr_xcol
5432     DO INSTEAD
5433     UPDATE query.expression SET
5434         id = NEW.id,
5435         parenthesize = NEW.parenthesize,
5436         parent_expr = NEW.parent_expr,
5437         seq_no = NEW.seq_no,
5438                 table_alias = NEW.table_alias,
5439                 column_name = NEW.column_name,
5440                 negate = NEW.negate
5441     WHERE
5442         id = OLD.id;
5443
5444 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
5445     ON DELETE TO query.expr_xcol
5446     DO INSTEAD
5447     DELETE FROM query.expression WHERE id = OLD.id;
5448
5449 -- Create updatable view for EXISTS expressions
5450
5451 CREATE OR REPLACE VIEW query.expr_xex AS
5452     SELECT
5453                 id,
5454                 parenthesize,
5455                 parent_expr,
5456                 seq_no,
5457                 subquery,
5458                 negate
5459     FROM
5460         query.expression
5461     WHERE
5462         type = 'xex';
5463
5464 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
5465     ON INSERT TO query.expr_xex
5466     DO INSTEAD
5467     INSERT INTO query.expression (
5468                 id,
5469                 type,
5470                 parenthesize,
5471                 parent_expr,
5472                 seq_no,
5473                 subquery,
5474                 negate
5475     ) VALUES (
5476         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5477         'xex',
5478         COALESCE(NEW.parenthesize, FALSE),
5479         NEW.parent_expr,
5480         COALESCE(NEW.seq_no, 1),
5481                 NEW.subquery,
5482                 COALESCE(NEW.negate, false)
5483     );
5484
5485 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
5486     ON UPDATE TO query.expr_xex
5487     DO INSTEAD
5488     UPDATE query.expression SET
5489         id = NEW.id,
5490         parenthesize = NEW.parenthesize,
5491         parent_expr = NEW.parent_expr,
5492         seq_no = NEW.seq_no,
5493                 subquery = NEW.subquery,
5494                 negate = NEW.negate
5495     WHERE
5496         id = OLD.id;
5497
5498 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
5499     ON DELETE TO query.expr_xex
5500     DO INSTEAD
5501     DELETE FROM query.expression WHERE id = OLD.id;
5502
5503 -- Create updatable view for function call expressions
5504
5505 CREATE OR REPLACE VIEW query.expr_xfunc AS
5506     SELECT
5507         id,
5508         parenthesize,
5509         parent_expr,
5510         seq_no,
5511         column_name,
5512         function_id,
5513         negate
5514     FROM
5515         query.expression
5516     WHERE
5517         type = 'xfunc';
5518
5519 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
5520     ON INSERT TO query.expr_xfunc
5521     DO INSTEAD
5522     INSERT INTO query.expression (
5523         id,
5524         type,
5525         parenthesize,
5526         parent_expr,
5527         seq_no,
5528         column_name,
5529         function_id,
5530         negate
5531     ) VALUES (
5532         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5533         'xfunc',
5534         COALESCE(NEW.parenthesize, FALSE),
5535         NEW.parent_expr,
5536         COALESCE(NEW.seq_no, 1),
5537         NEW.column_name,
5538         NEW.function_id,
5539         COALESCE(NEW.negate, false)
5540     );
5541
5542 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
5543     ON UPDATE TO query.expr_xfunc
5544     DO INSTEAD
5545     UPDATE query.expression SET
5546         id = NEW.id,
5547         parenthesize = NEW.parenthesize,
5548         parent_expr = NEW.parent_expr,
5549         seq_no = NEW.seq_no,
5550         column_name = NEW.column_name,
5551         function_id = NEW.function_id,
5552         negate = NEW.negate
5553     WHERE
5554         id = OLD.id;
5555
5556 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
5557     ON DELETE TO query.expr_xfunc
5558     DO INSTEAD
5559     DELETE FROM query.expression WHERE id = OLD.id;
5560
5561 -- Create updatable view for IN expressions
5562
5563 CREATE OR REPLACE VIEW query.expr_xin AS
5564     SELECT
5565                 id,
5566                 parenthesize,
5567                 parent_expr,
5568                 seq_no,
5569                 left_operand,
5570                 subquery,
5571                 negate
5572     FROM
5573         query.expression
5574     WHERE
5575         type = 'xin';
5576
5577 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
5578     ON INSERT TO query.expr_xin
5579     DO INSTEAD
5580     INSERT INTO query.expression (
5581                 id,
5582                 type,
5583                 parenthesize,
5584                 parent_expr,
5585                 seq_no,
5586                 left_operand,
5587                 subquery,
5588                 negate
5589     ) VALUES (
5590         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5591         'xin',
5592         COALESCE(NEW.parenthesize, FALSE),
5593         NEW.parent_expr,
5594         COALESCE(NEW.seq_no, 1),
5595                 NEW.left_operand,
5596                 NEW.subquery,
5597                 COALESCE(NEW.negate, false)
5598     );
5599
5600 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
5601     ON UPDATE TO query.expr_xin
5602     DO INSTEAD
5603     UPDATE query.expression SET
5604         id = NEW.id,
5605         parenthesize = NEW.parenthesize,
5606         parent_expr = NEW.parent_expr,
5607         seq_no = NEW.seq_no,
5608                 left_operand = NEW.left_operand,
5609                 subquery = NEW.subquery,
5610                 negate = NEW.negate
5611     WHERE
5612         id = OLD.id;
5613
5614 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
5615     ON DELETE TO query.expr_xin
5616     DO INSTEAD
5617     DELETE FROM query.expression WHERE id = OLD.id;
5618
5619 -- Create updatable view for IS NULL expressions
5620
5621 CREATE OR REPLACE VIEW query.expr_xisnull AS
5622     SELECT
5623                 id,
5624                 parenthesize,
5625                 parent_expr,
5626                 seq_no,
5627                 left_operand,
5628                 negate
5629     FROM
5630         query.expression
5631     WHERE
5632         type = 'xisnull';
5633
5634 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
5635     ON INSERT TO query.expr_xisnull
5636     DO INSTEAD
5637     INSERT INTO query.expression (
5638                 id,
5639                 type,
5640                 parenthesize,
5641                 parent_expr,
5642                 seq_no,
5643                 left_operand,
5644                 negate
5645     ) VALUES (
5646         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5647         'xisnull',
5648         COALESCE(NEW.parenthesize, FALSE),
5649         NEW.parent_expr,
5650         COALESCE(NEW.seq_no, 1),
5651                 NEW.left_operand,
5652                 COALESCE(NEW.negate, false)
5653     );
5654
5655 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
5656     ON UPDATE TO query.expr_xisnull
5657     DO INSTEAD
5658     UPDATE query.expression SET
5659         id = NEW.id,
5660         parenthesize = NEW.parenthesize,
5661         parent_expr = NEW.parent_expr,
5662         seq_no = NEW.seq_no,
5663                 left_operand = NEW.left_operand,
5664                 negate = NEW.negate
5665     WHERE
5666         id = OLD.id;
5667
5668 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
5669     ON DELETE TO query.expr_xisnull
5670     DO INSTEAD
5671     DELETE FROM query.expression WHERE id = OLD.id;
5672
5673 -- Create updatable view for NULL expressions
5674
5675 CREATE OR REPLACE VIEW query.expr_xnull AS
5676     SELECT
5677                 id,
5678                 parenthesize,
5679                 parent_expr,
5680                 seq_no,
5681                 negate
5682     FROM
5683         query.expression
5684     WHERE
5685         type = 'xnull';
5686
5687 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
5688     ON INSERT TO query.expr_xnull
5689     DO INSTEAD
5690     INSERT INTO query.expression (
5691                 id,
5692                 type,
5693                 parenthesize,
5694                 parent_expr,
5695                 seq_no,
5696                 negate
5697     ) VALUES (
5698         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5699         'xnull',
5700         COALESCE(NEW.parenthesize, FALSE),
5701         NEW.parent_expr,
5702         COALESCE(NEW.seq_no, 1),
5703                 COALESCE(NEW.negate, false)
5704     );
5705
5706 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
5707     ON UPDATE TO query.expr_xnull
5708     DO INSTEAD
5709     UPDATE query.expression SET
5710         id = NEW.id,
5711         parenthesize = NEW.parenthesize,
5712         parent_expr = NEW.parent_expr,
5713         seq_no = NEW.seq_no,
5714                 negate = NEW.negate
5715     WHERE
5716         id = OLD.id;
5717
5718 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
5719     ON DELETE TO query.expr_xnull
5720     DO INSTEAD
5721     DELETE FROM query.expression WHERE id = OLD.id;
5722
5723 -- Create updatable view for numeric literal expressions
5724
5725 CREATE OR REPLACE VIEW query.expr_xnum AS
5726     SELECT
5727                 id,
5728                 parenthesize,
5729                 parent_expr,
5730                 seq_no,
5731                 literal
5732     FROM
5733         query.expression
5734     WHERE
5735         type = 'xnum';
5736
5737 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
5738     ON INSERT TO query.expr_xnum
5739     DO INSTEAD
5740     INSERT INTO query.expression (
5741                 id,
5742                 type,
5743                 parenthesize,
5744                 parent_expr,
5745                 seq_no,
5746                 literal
5747     ) VALUES (
5748         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5749         'xnum',
5750         COALESCE(NEW.parenthesize, FALSE),
5751         NEW.parent_expr,
5752         COALESCE(NEW.seq_no, 1),
5753         NEW.literal
5754     );
5755
5756 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
5757     ON UPDATE TO query.expr_xnum
5758     DO INSTEAD
5759     UPDATE query.expression SET
5760         id = NEW.id,
5761         parenthesize = NEW.parenthesize,
5762         parent_expr = NEW.parent_expr,
5763         seq_no = NEW.seq_no,
5764         literal = NEW.literal
5765     WHERE
5766         id = OLD.id;
5767
5768 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
5769     ON DELETE TO query.expr_xnum
5770     DO INSTEAD
5771     DELETE FROM query.expression WHERE id = OLD.id;
5772
5773 -- Create updatable view for operator expressions
5774
5775 CREATE OR REPLACE VIEW query.expr_xop AS
5776     SELECT
5777                 id,
5778                 parenthesize,
5779                 parent_expr,
5780                 seq_no,
5781                 left_operand,
5782                 operator,
5783                 right_operand,
5784                 negate
5785     FROM
5786         query.expression
5787     WHERE
5788         type = 'xop';
5789
5790 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
5791     ON INSERT TO query.expr_xop
5792     DO INSTEAD
5793     INSERT INTO query.expression (
5794                 id,
5795                 type,
5796                 parenthesize,
5797                 parent_expr,
5798                 seq_no,
5799                 left_operand,
5800                 operator,
5801                 right_operand,
5802                 negate
5803     ) VALUES (
5804         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5805         'xop',
5806         COALESCE(NEW.parenthesize, FALSE),
5807         NEW.parent_expr,
5808         COALESCE(NEW.seq_no, 1),
5809                 NEW.left_operand,
5810                 NEW.operator,
5811                 NEW.right_operand,
5812                 COALESCE(NEW.negate, false)
5813     );
5814
5815 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
5816     ON UPDATE TO query.expr_xop
5817     DO INSTEAD
5818     UPDATE query.expression SET
5819         id = NEW.id,
5820         parenthesize = NEW.parenthesize,
5821         parent_expr = NEW.parent_expr,
5822         seq_no = NEW.seq_no,
5823                 left_operand = NEW.left_operand,
5824                 operator = NEW.operator,
5825                 right_operand = NEW.right_operand,
5826                 negate = NEW.negate
5827     WHERE
5828         id = OLD.id;
5829
5830 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
5831     ON DELETE TO query.expr_xop
5832     DO INSTEAD
5833     DELETE FROM query.expression WHERE id = OLD.id;
5834
5835 -- Create updatable view for series expressions
5836 -- i.e. series of expressions separated by operators
5837
5838 CREATE OR REPLACE VIEW query.expr_xser AS
5839     SELECT
5840                 id,
5841                 parenthesize,
5842                 parent_expr,
5843                 seq_no,
5844                 operator,
5845                 negate
5846     FROM
5847         query.expression
5848     WHERE
5849         type = 'xser';
5850
5851 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
5852     ON INSERT TO query.expr_xser
5853     DO INSTEAD
5854     INSERT INTO query.expression (
5855                 id,
5856                 type,
5857                 parenthesize,
5858                 parent_expr,
5859                 seq_no,
5860                 operator,
5861                 negate
5862     ) VALUES (
5863         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5864         'xser',
5865         COALESCE(NEW.parenthesize, FALSE),
5866         NEW.parent_expr,
5867         COALESCE(NEW.seq_no, 1),
5868                 NEW.operator,
5869                 COALESCE(NEW.negate, false)
5870     );
5871
5872 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
5873     ON UPDATE TO query.expr_xser
5874     DO INSTEAD
5875     UPDATE query.expression SET
5876         id = NEW.id,
5877         parenthesize = NEW.parenthesize,
5878         parent_expr = NEW.parent_expr,
5879         seq_no = NEW.seq_no,
5880                 operator = NEW.operator,
5881                 negate = NEW.negate
5882     WHERE
5883         id = OLD.id;
5884
5885 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
5886     ON DELETE TO query.expr_xser
5887     DO INSTEAD
5888     DELETE FROM query.expression WHERE id = OLD.id;
5889
5890 -- Create updatable view for string literal expressions
5891
5892 CREATE OR REPLACE VIEW query.expr_xstr AS
5893     SELECT
5894         id,
5895         parenthesize,
5896         parent_expr,
5897         seq_no,
5898         literal
5899     FROM
5900         query.expression
5901     WHERE
5902         type = 'xstr';
5903
5904 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
5905     ON INSERT TO query.expr_xstr
5906     DO INSTEAD
5907     INSERT INTO query.expression (
5908         id,
5909         type,
5910         parenthesize,
5911         parent_expr,
5912         seq_no,
5913         literal
5914     ) VALUES (
5915         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5916         'xstr',
5917         COALESCE(NEW.parenthesize, FALSE),
5918         NEW.parent_expr,
5919         COALESCE(NEW.seq_no, 1),
5920         NEW.literal
5921     );
5922
5923 CREATE OR REPLACE RULE query_expr_string_update_rule AS
5924     ON UPDATE TO query.expr_xstr
5925     DO INSTEAD
5926     UPDATE query.expression SET
5927         id = NEW.id,
5928         parenthesize = NEW.parenthesize,
5929         parent_expr = NEW.parent_expr,
5930         seq_no = NEW.seq_no,
5931         literal = NEW.literal
5932     WHERE
5933         id = OLD.id;
5934
5935 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
5936     ON DELETE TO query.expr_xstr
5937     DO INSTEAD
5938     DELETE FROM query.expression WHERE id = OLD.id;
5939
5940 -- Create updatable view for subquery expressions
5941
5942 CREATE OR REPLACE VIEW query.expr_xsubq AS
5943     SELECT
5944                 id,
5945                 parenthesize,
5946                 parent_expr,
5947                 seq_no,
5948                 subquery,
5949                 negate
5950     FROM
5951         query.expression
5952     WHERE
5953         type = 'xsubq';
5954
5955 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
5956     ON INSERT TO query.expr_xsubq
5957     DO INSTEAD
5958     INSERT INTO query.expression (
5959                 id,
5960                 type,
5961                 parenthesize,
5962                 parent_expr,
5963                 seq_no,
5964                 subquery,
5965                 negate
5966     ) VALUES (
5967         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5968         'xsubq',
5969         COALESCE(NEW.parenthesize, FALSE),
5970         NEW.parent_expr,
5971         COALESCE(NEW.seq_no, 1),
5972                 NEW.subquery,
5973                 COALESCE(NEW.negate, false)
5974     );
5975
5976 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
5977     ON UPDATE TO query.expr_xsubq
5978     DO INSTEAD
5979     UPDATE query.expression SET
5980         id = NEW.id,
5981         parenthesize = NEW.parenthesize,
5982         parent_expr = NEW.parent_expr,
5983         seq_no = NEW.seq_no,
5984                 subquery = NEW.subquery,
5985                 negate = NEW.negate
5986     WHERE
5987         id = OLD.id;
5988
5989 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
5990     ON DELETE TO query.expr_xsubq
5991     DO INSTEAD
5992     DELETE FROM query.expression WHERE id = OLD.id;
5993
5994 CREATE TABLE action.fieldset (
5995     id              SERIAL          PRIMARY KEY,
5996     owner           INT             NOT NULL REFERENCES actor.usr (id)
5997                                     DEFERRABLE INITIALLY DEFERRED,
5998     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
5999                                     DEFERRABLE INITIALLY DEFERRED,
6000     status          TEXT            NOT NULL
6001                                     CONSTRAINT valid_status CHECK ( status in
6002                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
6003     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
6004     scheduled_time  TIMESTAMPTZ,
6005     applied_time    TIMESTAMPTZ,
6006     classname       TEXT            NOT NULL, -- an IDL class name
6007     name            TEXT            NOT NULL,
6008     stored_query    INT             REFERENCES query.stored_query (id)
6009                                     DEFERRABLE INITIALLY DEFERRED,
6010     pkey_value      TEXT,
6011     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
6012     CONSTRAINT fieldset_one_or_the_other CHECK (
6013         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
6014         (pkey_value IS NOT NULL AND stored_query IS NULL)
6015     )
6016     -- the CHECK constraint means we can update the fields for a single
6017     -- row without all the extra overhead involved in a query
6018 );
6019
6020 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
6021 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
6022
6023 CREATE TABLE action.fieldset_col_val (
6024     id              SERIAL  PRIMARY KEY,
6025     fieldset        INT     NOT NULL REFERENCES action.fieldset
6026                                          ON DELETE CASCADE
6027                                          DEFERRABLE INITIALLY DEFERRED,
6028     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
6029     val             TEXT,              -- value for the column ... NULL means, well, NULL
6030     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
6031 );
6032
6033 CREATE OR REPLACE FUNCTION action.apply_fieldset(
6034         fieldset_id IN INT,        -- id from action.fieldset
6035         table_name  IN TEXT,       -- table to be updated
6036         pkey_name   IN TEXT,       -- name of primary key column in that table
6037         query       IN TEXT        -- query constructed by qstore (for query-based
6038                                    --    fieldsets only; otherwise null
6039 )
6040 RETURNS TEXT AS $$
6041 DECLARE
6042         statement TEXT;
6043         fs_status TEXT;
6044         fs_pkey_value TEXT;
6045         fs_query TEXT;
6046         sep CHAR;
6047         status_code TEXT;
6048         msg TEXT;
6049         update_count INT;
6050         cv RECORD;
6051 BEGIN
6052         -- Sanity checks
6053         IF fieldset_id IS NULL THEN
6054                 RETURN 'Fieldset ID parameter is NULL';
6055         END IF;
6056         IF table_name IS NULL THEN
6057                 RETURN 'Table name parameter is NULL';
6058         END IF;
6059         IF pkey_name IS NULL THEN
6060                 RETURN 'Primary key name parameter is NULL';
6061         END IF;
6062         --
6063         statement := 'UPDATE ' || table_name || ' SET';
6064         --
6065         SELECT
6066                 status,
6067                 quote_literal( pkey_value )
6068         INTO
6069                 fs_status,
6070                 fs_pkey_value
6071         FROM
6072                 action.fieldset
6073         WHERE
6074                 id = fieldset_id;
6075         --
6076         IF fs_status IS NULL THEN
6077                 RETURN 'No fieldset found for id = ' || fieldset_id;
6078         ELSIF fs_status = 'APPLIED' THEN
6079                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
6080         END IF;
6081         --
6082         sep := '';
6083         FOR cv IN
6084                 SELECT  col,
6085                                 val
6086                 FROM    action.fieldset_col_val
6087                 WHERE   fieldset = fieldset_id
6088         LOOP
6089                 statement := statement || sep || ' ' || cv.col
6090                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
6091                 sep := ',';
6092         END LOOP;
6093         --
6094         IF sep = '' THEN
6095                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
6096         END IF;
6097         --
6098         -- Add the WHERE clause.  This differs according to whether it's a
6099         -- single-row fieldset or a query-based fieldset.
6100         --
6101         IF query IS NULL        AND fs_pkey_value IS NULL THEN
6102                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
6103         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
6104             fs_query := rtrim( query, ';' );
6105             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
6106                          || fs_query || ' );';
6107         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
6108                 statement := statement || ' WHERE ' || pkey_name || ' = '
6109                                      || fs_pkey_value || ';';
6110         ELSE  -- both are not null
6111                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
6112         END IF;
6113         --
6114         -- Execute the update
6115         --
6116         BEGIN
6117                 EXECUTE statement;
6118                 GET DIAGNOSTICS update_count = ROW_COUNT;
6119                 --
6120                 IF UPDATE_COUNT > 0 THEN
6121                         status_code := 'APPLIED';
6122                         msg := NULL;
6123                 ELSE
6124                         status_code := 'ERROR';
6125                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
6126         END IF;
6127         EXCEPTION WHEN OTHERS THEN
6128                 status_code := 'ERROR';
6129                 msg := 'Unable to apply fieldset ' || fieldset_id
6130                            || ': ' || sqlerrm;
6131         END;
6132         --
6133         -- Update fieldset status
6134         --
6135         UPDATE action.fieldset
6136         SET status       = status_code,
6137             applied_time = now()
6138         WHERE id = fieldset_id;
6139         --
6140         RETURN msg;
6141 END;
6142 $$ LANGUAGE plpgsql;
6143
6144 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
6145 /**
6146  * Applies a specified fieldset, using a supplied table name and primary
6147  * key name.  The query parameter should be non-null only for
6148  * query-based fieldsets.
6149  *
6150  * Returns NULL if successful, or an error message if not.
6151  */
6152 $$;
6153
6154 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
6155
6156 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
6157     SELECT  u.hold,
6158             c.circ_lib,
6159             count(*)
6160       FROM  action.unfulfilled_hold_list u
6161             JOIN asset.copy c ON (c.id = u.current_copy)
6162       GROUP BY 1,2;
6163
6164 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
6165     SELECT  hold,
6166             min(count)
6167       FROM  action.unfulfilled_hold_loops
6168       GROUP BY 1;
6169
6170 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
6171     SELECT  DISTINCT l.*
6172       FROM  action.unfulfilled_hold_loops l
6173             JOIN action.unfulfilled_hold_min_loop m USING (hold)
6174       WHERE l.count = m.min;
6175
6176 ALTER TABLE asset.copy
6177 ADD COLUMN dummy_isbn TEXT;
6178
6179 ALTER TABLE auditor.asset_copy_history
6180 ADD COLUMN dummy_isbn TEXT;
6181
6182 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
6183 -- Add corresponding new column to auditor.asset_copy_history
6184
6185 ALTER TABLE asset.copy
6186         ADD COLUMN status_changed_time TIMESTAMPTZ;
6187
6188 ALTER TABLE auditor.asset_copy_history
6189         ADD COLUMN status_changed_time TIMESTAMPTZ;
6190
6191 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
6192 RETURNS TRIGGER AS $$
6193 BEGIN
6194     IF NEW.status <> OLD.status THEN
6195         NEW.status_changed_time := now();
6196     END IF;
6197     RETURN NEW;
6198 END;
6199 $$ LANGUAGE plpgsql;
6200
6201 CREATE TRIGGER acp_status_changed_trig
6202         BEFORE UPDATE ON asset.copy
6203         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
6204
6205 ALTER TABLE asset.copy
6206 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6207
6208 ALTER TABLE auditor.asset_copy_history
6209 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6210
6211 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
6212 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
6213
6214 DROP INDEX IF EXISTS asset.copy_barcode_key;
6215 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
6216
6217 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
6218 -- AFTER INSERT OR UPDATE ON asset.copy
6219
6220 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
6221 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
6222
6223 -- Moke mostly parallel changes to action.circulation
6224 -- and action.aged_circulation
6225
6226 ALTER TABLE action.circulation
6227 ADD COLUMN workstation INT
6228     REFERENCES actor.workstation
6229         ON DELETE SET NULL
6230         DEFERRABLE INITIALLY DEFERRED;
6231
6232 ALTER TABLE action.aged_circulation
6233 ADD COLUMN workstation INT;
6234
6235 ALTER TABLE action.circulation
6236 ADD COLUMN parent_circ BIGINT
6237         REFERENCES action.circulation(id)
6238         DEFERRABLE INITIALLY DEFERRED;
6239
6240 CREATE UNIQUE INDEX circ_parent_idx
6241 ON action.circulation( parent_circ )
6242 WHERE parent_circ IS NOT NULL;
6243
6244 ALTER TABLE action.aged_circulation
6245 ADD COLUMN parent_circ BIGINT;
6246
6247 ALTER TABLE action.circulation
6248 ADD COLUMN checkin_workstation INT
6249         REFERENCES actor.workstation(id)
6250         ON DELETE SET NULL
6251         DEFERRABLE INITIALLY DEFERRED;
6252
6253 ALTER TABLE action.aged_circulation
6254 ADD COLUMN checkin_workstation INT;
6255
6256 ALTER TABLE action.circulation
6257 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6258
6259 ALTER TABLE action.aged_circulation
6260 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6261
6262 CREATE INDEX action_circulation_target_copy_idx
6263 ON action.circulation (target_copy);
6264
6265 CREATE INDEX action_aged_circulation_target_copy_idx
6266 ON action.aged_circulation (target_copy);
6267
6268 ALTER TABLE action.circulation
6269 DROP CONSTRAINT circulation_stop_fines_check;
6270
6271 ALTER TABLE action.circulation
6272         ADD CONSTRAINT circulation_stop_fines_check
6273         CHECK (stop_fines IN (
6274         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
6275
6276 -- Hard due-date functionality
6277 CREATE TABLE config.hard_due_date (
6278         id          SERIAL      PRIMARY KEY,
6279         name        TEXT        NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
6280         ceiling_date    TIMESTAMPTZ NOT NULL,
6281         forceto     BOOL        NOT NULL,
6282         owner       INT         NOT NULL
6283 );
6284
6285 CREATE TABLE config.hard_due_date_values (
6286     id                  SERIAL      PRIMARY KEY,
6287     hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
6288                                     DEFERRABLE INITIALLY DEFERRED,
6289     ceiling_date        TIMESTAMPTZ NOT NULL,
6290     active_date         TIMESTAMPTZ NOT NULL
6291 );
6292
6293 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN hard_due_date INT REFERENCES config.hard_due_date (id);
6294
6295 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
6296 DECLARE
6297     temp_value  config.hard_due_date_values%ROWTYPE;
6298     updated     INT := 0;
6299 BEGIN
6300     FOR temp_value IN
6301       SELECT  DISTINCT ON (hard_due_date) *
6302         FROM  config.hard_due_date_values
6303         WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
6304         ORDER BY active_date DESC -- Latest (nearest to us) active time
6305    LOOP
6306         UPDATE  config.hard_due_date
6307           SET   ceiling_date = temp_value.ceiling_date
6308           WHERE id = temp_value.hard_due_date
6309                 AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd
6310
6311         IF FOUND THEN
6312             updated := updated + 1;
6313         END IF;
6314     END LOOP;
6315
6316     RETURN updated;
6317 END;
6318 $func$ LANGUAGE plpgsql;
6319
6320 -- Correct some long-standing misspellings involving variations of "recur"
6321
6322 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
6323 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6324
6325 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
6326 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6327
6328 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
6329 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
6330
6331 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
6332
6333 -- Might as well keep the comment in sync as well
6334 COMMENT ON TABLE config.rule_recurring_fine IS $$
6335 /*
6336  * Copyright (C) 2005  Georgia Public Library Service 
6337  * Mike Rylander <mrylander@gmail.com>
6338  *
6339  * Circulation Recurring Fine rules
6340  *
6341  * Each circulation is given a recurring fine amount based on one of
6342  * these rules.  The recurrence_interval should not be any shorter
6343  * than the interval between runs of the fine_processor.pl script
6344  * (which is run from CRON), or you could miss fines.
6345  * 
6346  *
6347  * ****
6348  *
6349  * This program is free software; you can redistribute it and/or
6350  * modify it under the terms of the GNU General Public License
6351  * as published by the Free Software Foundation; either version 2
6352  * of the License, or (at your option) any later version.
6353  *
6354  * This program is distributed in the hope that it will be useful,
6355  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6356  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6357  * GNU General Public License for more details.
6358  */
6359 $$;
6360
6361 -- Extend the name change to some related views:
6362
6363 DROP VIEW IF EXISTS reporter.overdue_circs;
6364
6365 CREATE OR REPLACE VIEW reporter.overdue_circs AS
6366 SELECT  *
6367   FROM  action.circulation
6368     WHERE checkin_time is null
6369                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
6370                                 AND due_date < now();
6371
6372 DROP VIEW IF EXISTS stats.fleshed_circulation;
6373
6374 DROP VIEW IF EXISTS stats.fleshed_copy;
6375
6376 CREATE VIEW stats.fleshed_copy AS
6377         SELECT  cp.*,
6378         CAST(cp.create_date AS DATE) AS create_date_day,
6379         CAST(cp.edit_date AS DATE) AS edit_date_day,
6380         DATE_TRUNC('hour', cp.create_date) AS create_date_hour,
6381         DATE_TRUNC('hour', cp.edit_date) AS edit_date_hour,
6382                 cn.label AS call_number_label,
6383                 cn.owning_lib,
6384                 rd.item_lang,
6385                 rd.item_type,
6386                 rd.item_form
6387         FROM    asset.copy cp
6388                 JOIN asset.call_number cn ON (cp.call_number = cn.id)
6389                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
6390
6391 CREATE VIEW stats.fleshed_circulation AS
6392         SELECT  c.*,
6393                 CAST(c.xact_start AS DATE) AS start_date_day,
6394                 CAST(c.xact_finish AS DATE) AS finish_date_day,
6395                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
6396                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
6397                 cp.call_number_label,
6398                 cp.owning_lib,
6399                 cp.item_lang,
6400                 cp.item_type,
6401                 cp.item_form
6402         FROM    action.circulation c
6403                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
6404
6405 -- Drop a view temporarily in order to alter action.all_circulation, upon
6406 -- which it is dependent.  We will recreate the view later.
6407
6408 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
6409
6410 -- You would think that CREATE OR REPLACE would be enough, but in testing
6411 -- PostgreSQL complained about renaming the columns in the view. So we
6412 -- drop the view first.
6413 DROP VIEW IF EXISTS action.all_circulation;
6414
6415 CREATE OR REPLACE VIEW action.all_circulation AS
6416     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6417         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6418         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6419         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6420         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6421         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6422       FROM  action.aged_circulation
6423             UNION ALL
6424     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,
6425         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,
6426         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6427         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6428         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6429         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6430         circ.parent_circ
6431       FROM  action.circulation circ
6432         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6433         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6434         JOIN actor.usr p ON (circ.usr = p.id)
6435         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6436         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
6437
6438 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
6439
6440 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
6441  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
6442    FROM asset."copy" cp
6443    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
6444    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
6445    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
6446   GROUP BY cp.id;
6447
6448 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
6449
6450 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
6451
6452 -- Rebuild dependent views
6453
6454 DROP VIEW IF EXISTS action.billable_circulations;
6455
6456 CREATE OR REPLACE VIEW action.billable_circulations AS
6457     SELECT  *
6458       FROM  action.circulation
6459       WHERE xact_finish IS NULL;
6460
6461 DROP VIEW IF EXISTS action.open_circulation;
6462
6463 CREATE OR REPLACE VIEW action.open_circulation AS
6464     SELECT  *
6465       FROM  action.circulation
6466       WHERE checkin_time IS NULL
6467       ORDER BY due_date;
6468
6469 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
6470 DECLARE
6471 found char := 'N';
6472 BEGIN
6473
6474     -- If there are any renewals for this circulation, don't archive or delete
6475     -- it yet.   We'll do so later, when we archive and delete the renewals.
6476
6477     SELECT 'Y' INTO found
6478     FROM action.circulation
6479     WHERE parent_circ = OLD.id
6480     LIMIT 1;
6481
6482     IF found = 'Y' THEN
6483         RETURN NULL;  -- don't delete
6484         END IF;
6485
6486     -- Archive a copy of the old row to action.aged_circulation
6487
6488     INSERT INTO action.aged_circulation
6489         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6490         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6491         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6492         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6493         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6494         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
6495       SELECT
6496         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6497         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6498         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6499         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6500         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6501         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6502         FROM action.all_circulation WHERE id = OLD.id;
6503
6504     RETURN OLD;
6505 END;
6506 $$ LANGUAGE 'plpgsql';
6507
6508 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
6509
6510 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
6511
6512 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
6513
6514 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$
6515 DECLARE
6516     current_requestor_group    permission.grp_tree%ROWTYPE;
6517     requestor_object    actor.usr%ROWTYPE;
6518     user_object        actor.usr%ROWTYPE;
6519     item_object        asset.copy%ROWTYPE;
6520     item_cn_object        asset.call_number%ROWTYPE;
6521     rec_descriptor        metabib.rec_descriptor%ROWTYPE;
6522     current_mp_weight    FLOAT;
6523     matchpoint_weight    FLOAT;
6524     tmp_weight        FLOAT;
6525     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
6526     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
6527 BEGIN
6528     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6529     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
6530     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6531     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6532     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
6533
6534     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
6535
6536     IF NOT FOUND THEN
6537         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
6538     ELSE
6539         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
6540     END IF;
6541
6542     LOOP 
6543         -- for each potential matchpoint for this ou and group ...
6544         FOR current_mp IN
6545             SELECT    m.*
6546               FROM    config.hold_matrix_matchpoint m
6547               WHERE    m.requestor_grp = current_requestor_group.id AND m.active
6548               ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
6549                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
6550                     CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
6551                     CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
6552                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
6553                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
6554
6555             current_mp_weight := 5.0;
6556
6557             IF current_mp.circ_modifier IS NOT NULL THEN
6558                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6559             END IF;
6560
6561             IF current_mp.marc_type IS NOT NULL THEN
6562                 IF item_object.circ_as_type IS NOT NULL THEN
6563                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6564                 ELSE
6565                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6566                 END IF;
6567             END IF;
6568
6569             IF current_mp.marc_form IS NOT NULL THEN
6570                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6571             END IF;
6572
6573             IF current_mp.marc_vr_format IS NOT NULL THEN
6574                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6575             END IF;
6576
6577             IF current_mp.juvenile_flag IS NOT NULL THEN
6578                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6579             END IF;
6580
6581             IF current_mp.ref_flag IS NOT NULL THEN
6582                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6583             END IF;
6584
6585
6586             -- caclulate the rule match weight
6587             IF current_mp.item_owning_ou IS NOT NULL THEN
6588                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
6589                 current_mp_weight := current_mp_weight - tmp_weight;
6590             END IF; 
6591
6592             IF current_mp.item_circ_ou IS NOT NULL THEN
6593                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
6594                 current_mp_weight := current_mp_weight - tmp_weight;
6595             END IF; 
6596
6597             IF current_mp.pickup_ou IS NOT NULL THEN
6598                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
6599                 current_mp_weight := current_mp_weight - tmp_weight;
6600             END IF; 
6601
6602             IF current_mp.request_ou IS NOT NULL THEN
6603                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
6604                 current_mp_weight := current_mp_weight - tmp_weight;
6605             END IF; 
6606
6607             IF current_mp.user_home_ou IS NOT NULL THEN
6608                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
6609                 current_mp_weight := current_mp_weight - tmp_weight;
6610             END IF; 
6611
6612             -- set the matchpoint if we found the best one
6613             IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
6614                 matchpoint = current_mp;
6615                 matchpoint_weight = current_mp_weight;
6616             END IF;
6617
6618         END LOOP;
6619
6620         EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6621
6622         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
6623     END LOOP;
6624
6625     RETURN matchpoint.id;
6626 END;
6627 $func$ LANGUAGE plpgsql;
6628
6629 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$
6630 DECLARE
6631     matchpoint_id        INT;
6632     user_object        actor.usr%ROWTYPE;
6633     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
6634     standing_penalty    config.standing_penalty%ROWTYPE;
6635     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
6636     transit_source        actor.org_unit%ROWTYPE;
6637     item_object        asset.copy%ROWTYPE;
6638     ou_skip              actor.org_unit_setting%ROWTYPE;
6639     result            action.matrix_test_result;
6640     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
6641     hold_count        INT;
6642     hold_transit_prox    INT;
6643     frozen_hold_count    INT;
6644     context_org_list    INT[];
6645     done            BOOL := FALSE;
6646 BEGIN
6647     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6648     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
6649
6650     result.success := TRUE;
6651
6652     -- Fail if we couldn't find a user
6653     IF user_object.id IS NULL THEN
6654         result.fail_part := 'no_user';
6655         result.success := FALSE;
6656         done := TRUE;
6657         RETURN NEXT result;
6658         RETURN;
6659     END IF;
6660
6661     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6662
6663     -- Fail if we couldn't find a copy
6664     IF item_object.id IS NULL THEN
6665         result.fail_part := 'no_item';
6666         result.success := FALSE;
6667         done := TRUE;
6668         RETURN NEXT result;
6669         RETURN;
6670     END IF;
6671
6672     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
6673     result.matchpoint := matchpoint_id;
6674
6675     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
6676
6677     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
6678     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
6679         result.fail_part := 'circ.holds.target_skip_me';
6680         result.success := FALSE;
6681         done := TRUE;
6682         RETURN NEXT result;
6683         RETURN;
6684     END IF;
6685
6686     -- Fail if user is barred
6687     IF user_object.barred IS TRUE THEN
6688         result.fail_part := 'actor.usr.barred';
6689         result.success := FALSE;
6690         done := TRUE;
6691         RETURN NEXT result;
6692         RETURN;
6693     END IF;
6694
6695     -- Fail if we couldn't find any matchpoint (requires a default)
6696     IF matchpoint_id IS NULL THEN
6697         result.fail_part := 'no_matchpoint';
6698         result.success := FALSE;
6699         done := TRUE;
6700         RETURN NEXT result;
6701         RETURN;
6702     END IF;
6703
6704     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
6705
6706     IF hold_test.holdable IS FALSE THEN
6707         result.fail_part := 'config.hold_matrix_test.holdable';
6708         result.success := FALSE;
6709         done := TRUE;
6710         RETURN NEXT result;
6711     END IF;
6712
6713     IF hold_test.transit_range IS NOT NULL THEN
6714         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
6715         IF hold_test.distance_is_from_owner THEN
6716             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;
6717         ELSE
6718             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
6719         END IF;
6720
6721         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
6722
6723         IF NOT FOUND THEN
6724             result.fail_part := 'transit_range';
6725             result.success := FALSE;
6726             done := TRUE;
6727             RETURN NEXT result;
6728         END IF;
6729     END IF;
6730  
6731     IF NOT retargetting THEN
6732         FOR standing_penalty IN
6733             SELECT  DISTINCT csp.*
6734               FROM  actor.usr_standing_penalty usp
6735                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6736               WHERE usr = match_user
6737                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6738                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6739                     AND csp.block_list LIKE '%HOLD%' LOOP
6740     
6741             result.fail_part := standing_penalty.name;
6742             result.success := FALSE;
6743             done := TRUE;
6744             RETURN NEXT result;
6745         END LOOP;
6746     
6747         IF hold_test.stop_blocked_user IS TRUE THEN
6748             FOR standing_penalty IN
6749                 SELECT  DISTINCT csp.*
6750                   FROM  actor.usr_standing_penalty usp
6751                         JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6752                   WHERE usr = match_user
6753                         AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6754                         AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6755                         AND csp.block_list LIKE '%CIRC%' LOOP
6756         
6757                 result.fail_part := standing_penalty.name;
6758                 result.success := FALSE;
6759                 done := TRUE;
6760                 RETURN NEXT result;
6761             END LOOP;
6762         END IF;
6763     
6764         IF hold_test.max_holds IS NOT NULL THEN
6765             SELECT    INTO hold_count COUNT(*)
6766               FROM    action.hold_request
6767               WHERE    usr = match_user
6768                 AND fulfillment_time IS NULL
6769                 AND cancel_time IS NULL
6770                 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
6771     
6772             IF hold_count >= hold_test.max_holds THEN
6773                 result.fail_part := 'config.hold_matrix_test.max_holds';
6774                 result.success := FALSE;
6775                 done := TRUE;
6776                 RETURN NEXT result;
6777             END IF;
6778         END IF;
6779     END IF;
6780
6781     IF item_object.age_protect IS NOT NULL THEN
6782         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
6783
6784         IF item_object.create_date + age_protect_object.age > NOW() THEN
6785             IF hold_test.distance_is_from_owner THEN
6786                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
6787             ELSE
6788                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
6789             END IF;
6790
6791             IF hold_transit_prox > age_protect_object.prox THEN
6792                 result.fail_part := 'config.rule_age_hold_protect.prox';
6793                 result.success := FALSE;
6794                 done := TRUE;
6795                 RETURN NEXT result;
6796             END IF;
6797         END IF;
6798     END IF;
6799
6800     IF NOT done THEN
6801         RETURN NEXT result;
6802     END IF;
6803
6804     RETURN;
6805 END;
6806 $func$ LANGUAGE plpgsql;
6807
6808 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$
6809     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE);
6810 $func$ LANGUAGE SQL;
6811
6812 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$
6813     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
6814 $func$ LANGUAGE SQL;
6815
6816 -- New post-delete trigger to propagate deletions to parent(s)
6817
6818 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
6819 BEGIN
6820
6821     -- Having deleted a renewal, we can delete the original circulation (or a previous
6822     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
6823     -- deletion of any prior parents, etc. recursively.
6824
6825     IF OLD.parent_circ IS NOT NULL THEN
6826         DELETE FROM action.circulation
6827         WHERE id = OLD.parent_circ;
6828     END IF;
6829
6830     RETURN OLD;
6831 END;
6832 $$ LANGUAGE 'plpgsql';
6833
6834 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
6835 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
6836
6837 -- This only gets inserted if there are no other id > 100 billing types
6838 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;
6839 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
6840
6841 -- Populate xact_type column in the materialized version of billable_xact_summary
6842
6843 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
6844 BEGIN
6845         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
6846                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
6847         RETURN NEW;
6848 END;
6849 $$ LANGUAGE PLPGSQL;
6850  
6851 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
6852 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
6853  
6854 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
6855 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
6856
6857 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
6858     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;
6859
6860 -- Generate the equivalent of compound subject entries from the existing rows
6861 -- so that we don't have to laboriously reindex them
6862
6863 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
6864 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
6865 --
6866 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
6867 --
6868 --INSERT INTO metabib.subject_field_entry (source, field, value)
6869 --    SELECT source, (
6870 --            SELECT id 
6871 --            FROM config.metabib_field
6872 --            WHERE field_class = 'subject' AND name = 'complete'
6873 --        ), 
6874 --        ARRAY_TO_STRING ( 
6875 --            ARRAY (
6876 --                SELECT value 
6877 --                FROM metabib.subject_field_entry msfe
6878 --                WHERE msfe.source = groupee.source
6879 --                ORDER BY source 
6880 --            ), ' ' 
6881 --        ) AS grouped
6882 --    FROM ( 
6883 --        SELECT source
6884 --        FROM metabib.subject_field_entry
6885 --        GROUP BY source
6886 --    ) AS groupee;
6887
6888 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
6889 DECLARE
6890         prev_billing    money.billing%ROWTYPE;
6891         old_billing     money.billing%ROWTYPE;
6892 BEGIN
6893         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
6894         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
6895
6896         IF OLD.id = old_billing.id THEN
6897                 UPDATE  money.materialized_billable_xact_summary
6898                   SET   last_billing_ts = prev_billing.billing_ts,
6899                         last_billing_note = prev_billing.note,
6900                         last_billing_type = prev_billing.billing_type
6901                   WHERE id = OLD.xact;
6902         END IF;
6903
6904         IF NOT OLD.voided THEN
6905                 UPDATE  money.materialized_billable_xact_summary
6906                   SET   total_owed = total_owed - OLD.amount,
6907                         balance_owed = balance_owed + OLD.amount
6908                   WHERE id = OLD.xact;
6909         END IF;
6910
6911         RETURN OLD;
6912 END;
6913 $$ LANGUAGE PLPGSQL;
6914
6915 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
6916 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
6917
6918 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
6919     use Unicode::Normalize;
6920     use Encode;
6921
6922     # When working with Unicode data, the first step is to decode it to
6923     # a byte string; after that, lowercasing is safe
6924     my $txt = lc(decode_utf8(shift));
6925     my $sf = shift;
6926
6927     $txt = NFD($txt);
6928     $txt =~ s/\pM+//go;     # Remove diacritics
6929
6930     $txt =~ s/\xE6/AE/go;   # Convert ae digraph
6931     $txt =~ s/\x{153}/OE/go;# Convert oe digraph
6932     $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
6933
6934     $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
6935     $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
6936
6937     $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;         # Convert Latin and Greek
6938     $txt =~ tr/\x{2113}\xF0\x{111}\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LDD /;     # Convert Misc
6939     $txt =~ tr/\'\[\]\|//d;                         # Remove Misc
6940
6941     if ($sf && $sf =~ /^a/o) {
6942         my $commapos = index($txt,',');
6943         if ($commapos > -1) {
6944             if ($commapos != length($txt) - 1) {
6945                 my @list = split /,/, $txt;
6946                 my $first = shift @list;
6947                 $txt = $first . ',' . join(' ', @list);
6948             } else {
6949                 $txt =~ s/,/ /go;
6950             }
6951         }
6952     } else {
6953         $txt =~ s/,/ /go;
6954     }
6955
6956     $txt =~ s/\s+/ /go;     # Compress multiple spaces
6957     $txt =~ s/^\s+//o;      # Remove leading space
6958     $txt =~ s/\s+$//o;      # Remove trailing space
6959
6960     # Encoding the outgoing string is good practice, but not strictly
6961     # necessary in this case because we've stripped everything from it
6962     return encode_utf8($txt);
6963 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
6964
6965 -- Some handy functions, based on existing ones, to provide optional ingest normalization
6966
6967 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6968         SELECT SUBSTRING($1,$2);
6969 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6970
6971 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6972         SELECT SUBSTRING($1,1,$2);
6973 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6974
6975 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
6976         SELECT public.naco_normalize($1,'a');
6977 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6978
6979 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
6980         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
6981 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6982
6983 -- And ... a table in which to register them
6984
6985 CREATE TABLE config.index_normalizer (
6986         id              SERIAL  PRIMARY KEY,
6987         name            TEXT    UNIQUE NOT NULL,
6988         description     TEXT,
6989         func            TEXT    NOT NULL,
6990         param_count     INT     NOT NULL DEFAULT 0
6991 );
6992
6993 CREATE TABLE config.metabib_field_index_norm_map (
6994         id      SERIAL  PRIMARY KEY,
6995         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6996         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6997         params  TEXT,
6998         pos     INT     NOT NULL DEFAULT 0
6999 );
7000
7001 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7002         'NACO Normalize',
7003         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
7004         'naco_normalize',
7005         0
7006 );
7007
7008 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7009         'Normalize date range',
7010         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
7011         'split_date_range',
7012         1
7013 );
7014
7015 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7016         'NACO Normalize -- retain first comma',
7017         '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.',
7018         'naco_normalize_keep_comma',
7019         0
7020 );
7021
7022 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7023         'Strip Diacritics',
7024         'Convert text to NFD form and remove non-spacing combining marks.',
7025         'remove_diacritics',
7026         0
7027 );
7028
7029 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7030         'Up-case',
7031         'Convert text upper case.',
7032         'uppercase',
7033         0
7034 );
7035
7036 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7037         'Down-case',
7038         'Convert text lower case.',
7039         'lowercase',
7040         0
7041 );
7042
7043 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7044         'Extract Dewey-like number',
7045         'Extract a string of numeric characters ther resembles a DDC number.',
7046         'call_number_dewey',
7047         0
7048 );
7049
7050 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7051         'Left truncation',
7052         'Discard the specified number of characters from the left side of the string.',
7053         'left_trunc',
7054         1
7055 );
7056
7057 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7058         'Right truncation',
7059         'Include only the specified number of characters from the left side of the string.',
7060         'right_trunc',
7061         1
7062 );
7063
7064 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7065         'First word',
7066         'Include only the first space-separated word of a string.',
7067         'first_word',
7068         0
7069 );
7070
7071 INSERT INTO config.metabib_field_index_norm_map (field,norm)
7072         SELECT  m.id,
7073                 i.id
7074           FROM  config.metabib_field m,
7075                 config.index_normalizer i
7076           WHERE i.func IN ('naco_normalize','split_date_range');
7077
7078 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
7079 DECLARE
7080     normalizer      RECORD;
7081     value           TEXT := '';
7082 BEGIN
7083
7084     value := NEW.value;
7085
7086     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7087         FOR normalizer IN
7088             SELECT  n.func AS func,
7089                     n.param_count AS param_count,
7090                     m.params AS params
7091               FROM  config.index_normalizer n
7092                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7093               WHERE field = NEW.field AND m.pos < 0
7094               ORDER BY m.pos LOOP
7095                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7096                     quote_literal( value ) ||
7097                     CASE
7098                         WHEN normalizer.param_count > 0
7099                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7100                             ELSE ''
7101                         END ||
7102                     ')' INTO value;
7103
7104         END LOOP;
7105
7106         NEW.value := value;
7107     END IF;
7108
7109     IF NEW.index_vector = ''::tsvector THEN
7110         RETURN NEW;
7111     END IF;
7112
7113     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7114         FOR normalizer IN
7115             SELECT  n.func AS func,
7116                     n.param_count AS param_count,
7117                     m.params AS params
7118               FROM  config.index_normalizer n
7119                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7120               WHERE field = NEW.field AND m.pos >= 0
7121               ORDER BY m.pos LOOP
7122                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7123                     quote_literal( value ) ||
7124                     CASE
7125                         WHEN normalizer.param_count > 0
7126                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7127                             ELSE ''
7128                         END ||
7129                     ')' INTO value;
7130
7131         END LOOP;
7132     END IF;
7133
7134     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
7135         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
7136     ELSE
7137         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
7138     END IF;
7139
7140     RETURN NEW;
7141 END;
7142 $$ LANGUAGE PLPGSQL;
7143
7144 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7145
7146 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7147
7148 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7149     SELECT  ARRAY_TO_STRING(
7150                 oils_xpath(
7151                     $1 ||
7152                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
7153                     $2,
7154                     $4
7155                 ),
7156                 $3
7157             );
7158 $func$ LANGUAGE SQL IMMUTABLE;
7159
7160 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
7161     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
7162 $func$ LANGUAGE SQL IMMUTABLE;
7163
7164 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7165     SELECT oils_xpath_string( $1, $2, '', $3 );
7166 $func$ LANGUAGE SQL IMMUTABLE;
7167
7168 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
7169     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
7170 $func$ LANGUAGE SQL IMMUTABLE;
7171
7172 CREATE TYPE metabib.field_entry_template AS (
7173         field_class     TEXT,
7174         field           INT,
7175         source          BIGINT,
7176         value           TEXT
7177 );
7178
7179 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7180   use strict;
7181
7182   use XML::LibXSLT;
7183   use XML::LibXML;
7184
7185   my $doc = shift;
7186   my $xslt = shift;
7187
7188   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7189   # methods of parsing XML documents and stylesheets, in the hopes of broader
7190   # compatibility with distributions
7191   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7192
7193   # Cache the XML parser, if we do not already have one
7194   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7195     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7196
7197   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7198
7199   # Cache the XSLT processor, if we do not already have one
7200   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7201     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7202
7203   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7204     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7205
7206   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7207     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7208
7209   return $stylesheet->output_string(
7210     $stylesheet->transform(
7211       $parser->parse_string($doc)
7212     )
7213   );
7214
7215 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7216
7217 -- Add two columns so that the following function will compile.
7218 -- Eventually the label column will be NOT NULL, but not yet.
7219 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7220 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7221
7222 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7223 DECLARE
7224     bib     biblio.record_entry%ROWTYPE;
7225     idx     config.metabib_field%ROWTYPE;
7226     xfrm        config.xml_transform%ROWTYPE;
7227     prev_xfrm   TEXT;
7228     transformed_xml TEXT;
7229     xml_node    TEXT;
7230     xml_node_list   TEXT[];
7231     facet_text  TEXT;
7232     raw_text    TEXT;
7233     curr_text   TEXT;
7234     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7235     output_row  metabib.field_entry_template%ROWTYPE;
7236 BEGIN
7237
7238     -- Get the record
7239     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7240
7241     -- Loop over the indexing entries
7242     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7243
7244         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7245
7246         -- See if we can skip the XSLT ... it's expensive
7247         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7248             -- Can't skip the transform
7249             IF xfrm.xslt <> '---' THEN
7250                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7251             ELSE
7252                 transformed_xml := bib.marc;
7253             END IF;
7254
7255             prev_xfrm := xfrm.name;
7256         END IF;
7257
7258         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7259
7260         raw_text := NULL;
7261         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7262             CONTINUE WHEN xml_node !~ E'^\\s*<';
7263
7264             curr_text := ARRAY_TO_STRING(
7265                 oils_xpath( '//text()',
7266                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7267                         REGEXP_REPLACE( -- This escapes embeded <s
7268                             xml_node,
7269                             $re$(>[^<]+)(<)([^>]+<)$re$,
7270                             E'\\1&lt;\\3',
7271                             'g'
7272                         ),
7273                         '&(?!amp;)',
7274                         '&amp;',
7275                         'g'
7276                     )
7277                 ),
7278                 ' '
7279             );
7280
7281             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7282
7283             IF raw_text IS NOT NULL THEN
7284                 raw_text := raw_text || joiner;
7285             END IF;
7286
7287             raw_text := COALESCE(raw_text,'') || curr_text;
7288
7289             -- insert raw node text for faceting
7290             IF idx.facet_field THEN
7291
7292                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7293                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7294                 ELSE
7295                     facet_text := curr_text;
7296                 END IF;
7297
7298                 output_row.field_class = idx.field_class;
7299                 output_row.field = -1 * idx.id;
7300                 output_row.source = rid;
7301                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7302
7303                 RETURN NEXT output_row;
7304             END IF;
7305
7306         END LOOP;
7307
7308         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7309
7310         -- insert combined node text for searching
7311         IF idx.search_field THEN
7312             output_row.field_class = idx.field_class;
7313             output_row.field = idx.id;
7314             output_row.source = rid;
7315             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7316
7317             RETURN NEXT output_row;
7318         END IF;
7319
7320     END LOOP;
7321
7322 END;
7323 $func$ LANGUAGE PLPGSQL;
7324
7325 -- default to a space joiner
7326 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7327         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7328 $func$ LANGUAGE SQL;
7329
7330 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7331
7332 use MARC::Record;
7333 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7334
7335 my $xml = shift;
7336 my $r = MARC::Record->new_from_xml( $xml );
7337
7338 return_next( { tag => 'LDR', value => $r->leader } );
7339
7340 for my $f ( $r->fields ) {
7341     if ($f->is_control_field) {
7342         return_next({ tag => $f->tag, value => $f->data });
7343     } else {
7344         for my $s ($f->subfields) {
7345             return_next({
7346                 tag      => $f->tag,
7347                 ind1     => $f->indicator(1),
7348                 ind2     => $f->indicator(2),
7349                 subfield => $s->[0],
7350                 value    => $s->[1]
7351             });
7352
7353             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7354                 my $trim = $f->indicator(2) || 0;
7355                 return_next({
7356                     tag      => 'tnf',
7357                     ind1     => $f->indicator(1),
7358                     ind2     => $f->indicator(2),
7359                     subfield => 'a',
7360                     value    => substr( $s->[1], $trim )
7361                 });
7362             }
7363         }
7364     }
7365 }
7366
7367 return undef;
7368
7369 $func$ LANGUAGE PLPERLU;
7370
7371 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7372 DECLARE
7373     bib biblio.record_entry%ROWTYPE;
7374     output  metabib.full_rec%ROWTYPE;
7375     field   RECORD;
7376 BEGIN
7377     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7378
7379     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7380         output.record := rid;
7381         output.ind1 := field.ind1;
7382         output.ind2 := field.ind2;
7383         output.tag := field.tag;
7384         output.subfield := field.subfield;
7385         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7386             output.value := naco_normalize(field.value, field.subfield);
7387         ELSE
7388             output.value := field.value;
7389         END IF;
7390
7391         CONTINUE WHEN output.value IS NULL;
7392
7393         RETURN NEXT output;
7394     END LOOP;
7395 END;
7396 $func$ LANGUAGE PLPGSQL;
7397
7398 -- functions to create auditor objects
7399
7400 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7401 BEGIN
7402     EXECUTE $$
7403         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7404     $$;
7405         RETURN TRUE;
7406 END;
7407 $creator$ LANGUAGE 'plpgsql';
7408
7409 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7410 BEGIN
7411     EXECUTE $$
7412         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7413             audit_id    BIGINT                          PRIMARY KEY,
7414             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7415             audit_action        TEXT                            NOT NULL,
7416             LIKE $$ || sch || $$.$$ || tbl || $$
7417         );
7418     $$;
7419         RETURN TRUE;
7420 END;
7421 $creator$ LANGUAGE 'plpgsql';
7422
7423 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7424 BEGIN
7425     EXECUTE $$
7426         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7427         RETURNS TRIGGER AS $func$
7428         BEGIN
7429             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7430                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7431                     now(),
7432                     SUBSTR(TG_OP,1,1),
7433                     OLD.*;
7434             RETURN NULL;
7435         END;
7436         $func$ LANGUAGE 'plpgsql';
7437     $$;
7438         RETURN TRUE;
7439 END;
7440 $creator$ LANGUAGE 'plpgsql';
7441
7442 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7443 BEGIN
7444     EXECUTE $$
7445         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7446             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7447             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7448     $$;
7449         RETURN TRUE;
7450 END;
7451 $creator$ LANGUAGE 'plpgsql';
7452
7453 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7454 BEGIN
7455     EXECUTE $$
7456         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7457             SELECT      -1, now() as audit_time, '-' as audit_action, *
7458               FROM      $$ || sch || $$.$$ || tbl || $$
7459                 UNION ALL
7460             SELECT      *
7461               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7462     $$;
7463         RETURN TRUE;
7464 END;
7465 $creator$ LANGUAGE 'plpgsql';
7466
7467 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7468
7469 -- The main event
7470
7471 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7472 BEGIN
7473     PERFORM auditor.create_auditor_seq(sch, tbl);
7474     PERFORM auditor.create_auditor_history(sch, tbl);
7475     PERFORM auditor.create_auditor_func(sch, tbl);
7476     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7477     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7478     RETURN TRUE;
7479 END;
7480 $creator$ LANGUAGE 'plpgsql';
7481
7482 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7483
7484 ALTER TABLE action.hold_request
7485 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7486
7487 ALTER TABLE action.hold_request
7488 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7489
7490 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7491
7492 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7493
7494 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7495
7496 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7497
7498 -- Add claims_never_checked_out_count to actor.usr, related history
7499
7500 ALTER TABLE actor.usr ADD COLUMN
7501         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7502
7503 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7504         claims_never_checked_out_count INT;
7505
7506 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7507
7508 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7509
7510 -----------
7511
7512 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7513 BEGIN
7514         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7515                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7516                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7517                 END IF;
7518                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7519                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7520                 END IF;
7521                 IF NEW.stop_fines = 'LOST' THEN
7522                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7523                 END IF;
7524         END IF;
7525         RETURN NEW;
7526 END;
7527 $$ LANGUAGE 'plpgsql';
7528
7529 -- Create new table acq.fund_allocation_percent
7530 -- Populate it from acq.fund_allocation
7531 -- Convert all percentages to amounts in acq.fund_allocation
7532
7533 CREATE TABLE acq.fund_allocation_percent
7534 (
7535     id                   SERIAL            PRIMARY KEY,
7536     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7537                                                DEFERRABLE INITIALLY DEFERRED,
7538     org                  INT               NOT NULL REFERENCES actor.org_unit
7539                                                DEFERRABLE INITIALLY DEFERRED,
7540     fund_code            TEXT,
7541     percent              NUMERIC           NOT NULL,
7542     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7543                                                DEFERRABLE INITIALLY DEFERRED,
7544     note                 TEXT,
7545     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7546     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7547     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7548 );
7549
7550 -- Trigger function to validate combination of org_unit and fund_code
7551
7552 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7553 RETURNS TRIGGER AS $$
7554 --
7555 DECLARE
7556 --
7557 dummy int := 0;
7558 --
7559 BEGIN
7560     SELECT
7561         1
7562     INTO
7563         dummy
7564     FROM
7565         acq.fund
7566     WHERE
7567         org = NEW.org
7568         AND code = NEW.fund_code
7569         LIMIT 1;
7570     --
7571     IF dummy = 1 then
7572         RETURN NEW;
7573     ELSE
7574         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7575     END IF;
7576 END;
7577 $$ LANGUAGE plpgsql;
7578
7579 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7580     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7581     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7582
7583 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7584 RETURNS TRIGGER AS $$
7585 DECLARE
7586 --
7587 total_percent numeric;
7588 --
7589 BEGIN
7590     SELECT
7591         sum( percent )
7592     INTO
7593         total_percent
7594     FROM
7595         acq.fund_allocation_percent AS fap
7596     WHERE
7597         fap.funding_source = NEW.funding_source;
7598     --
7599     IF total_percent > 100 THEN
7600         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7601             NEW.funding_source;
7602     ELSE
7603         RETURN NEW;
7604     END IF;
7605 END;
7606 $$ LANGUAGE plpgsql;
7607
7608 CREATE TRIGGER acqfap_limit_100_trig
7609     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7610     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7611
7612 -- Populate new table from acq.fund_allocation
7613
7614 INSERT INTO acq.fund_allocation_percent
7615 (
7616     funding_source,
7617     org,
7618     fund_code,
7619     percent,
7620     allocator,
7621     note,
7622     create_time
7623 )
7624     SELECT
7625         fa.funding_source,
7626         fund.org,
7627         fund.code,
7628         fa.percent,
7629         fa.allocator,
7630         fa.note,
7631         fa.create_time
7632     FROM
7633         acq.fund_allocation AS fa
7634             INNER JOIN acq.fund AS fund
7635                 ON ( fa.fund = fund.id )
7636     WHERE
7637         fa.percent is not null
7638     ORDER BY
7639         fund.org;
7640
7641 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7642
7643 -- Algorithm to apply to each funding source:
7644
7645 -- 1. Add up the credits.
7646 -- 2. Add up the percentages.
7647 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7648 --    fractional cents from the result.  This is the total amount to be allocated.
7649 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7650 --    fractional cents to get a preliminary amount.
7651 -- 5. Add up the preliminary amounts for all the allocations.
7652 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7653 --    number of residual cents (resulting from having dropped fractional cents) that
7654 --    must be distributed across the funds in order to make the total of the amounts
7655 --    match the total allocation.
7656 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7657 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7658 --    for each successive fund, until all the residual cents have been exhausted.
7659
7660 -- Result: the sum of the individual allocations now equals the total to be allocated,
7661 -- to the penny.  The individual amounts match the percentages as closely as possible,
7662 -- given the constraint that the total must match.
7663
7664 CREATE OR REPLACE FUNCTION acq.apply_percents()
7665 RETURNS VOID AS $$
7666 declare
7667 --
7668 tot              RECORD;
7669 fund             RECORD;
7670 tot_cents        INTEGER;
7671 src              INTEGER;
7672 id               INTEGER[];
7673 curr_id          INTEGER;
7674 pennies          NUMERIC[];
7675 curr_amount      NUMERIC;
7676 i                INTEGER;
7677 total_of_floors  INTEGER;
7678 total_percent    NUMERIC;
7679 total_allocation INTEGER;
7680 residue          INTEGER;
7681 --
7682 begin
7683         RAISE NOTICE 'Applying percents';
7684         FOR tot IN
7685                 SELECT
7686                         fsrc.funding_source,
7687                         sum( fsrc.amount ) AS total
7688                 FROM
7689                         acq.funding_source_credit AS fsrc
7690                 WHERE fsrc.funding_source IN
7691                         ( SELECT DISTINCT fa.funding_source
7692                           FROM acq.fund_allocation AS fa
7693                           WHERE fa.percent IS NOT NULL )
7694                 GROUP BY
7695                         fsrc.funding_source
7696         LOOP
7697                 tot_cents = floor( tot.total * 100 );
7698                 src = tot.funding_source;
7699                 RAISE NOTICE 'Funding source % total %',
7700                         src, tot_cents;
7701                 i := 0;
7702                 total_of_floors := 0;
7703                 total_percent := 0;
7704                 --
7705                 FOR fund in
7706                         SELECT
7707                                 fa.id,
7708                                 fa.percent,
7709                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7710                         FROM
7711                                 acq.fund_allocation AS fa
7712                         WHERE
7713                                 fa.funding_source = src
7714                                 AND fa.percent IS NOT NULL
7715                         ORDER BY
7716                                 mod( fa.percent * tot_cents / 100, 1 ),
7717                                 fa.fund,
7718                                 fa.id
7719                 LOOP
7720                         RAISE NOTICE '   %: %',
7721                                 fund.id,
7722                                 fund.floor_pennies;
7723                         i := i + 1;
7724                         id[i] = fund.id;
7725                         pennies[i] = fund.floor_pennies;
7726                         total_percent := total_percent + fund.percent;
7727                         total_of_floors := total_of_floors + pennies[i];
7728                 END LOOP;
7729                 total_allocation := floor( total_percent * tot_cents /100 );
7730                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7731                 residue := total_allocation - total_of_floors;
7732                 RAISE NOTICE 'Residue: %', residue;
7733                 --
7734                 -- Post the calculated amounts, revising as needed to
7735                 -- distribute the rounding error
7736                 --
7737                 WHILE i > 0 LOOP
7738                         IF residue > 0 THEN
7739                                 pennies[i] = pennies[i] + 1;
7740                                 residue := residue - 1;
7741                         END IF;
7742                         --
7743                         -- Post amount
7744                         --
7745                         curr_id     := id[i];
7746                         curr_amount := trunc( pennies[i] / 100, 2 );
7747                         --
7748                         UPDATE
7749                                 acq.fund_allocation AS fa
7750                         SET
7751                                 amount = curr_amount,
7752                                 percent = NULL
7753                         WHERE
7754                                 fa.id = curr_id;
7755                         --
7756                         RAISE NOTICE '   ID % and amount %',
7757                                 curr_id,
7758                                 curr_amount;
7759                         i = i - 1;
7760                 END LOOP;
7761         END LOOP;
7762 end;
7763 $$ LANGUAGE 'plpgsql';
7764
7765 -- Run the temporary function
7766
7767 select * from acq.apply_percents();
7768
7769 -- Drop the temporary function now that we're done with it
7770
7771 DROP FUNCTION IF EXISTS acq.apply_percents();
7772
7773 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7774
7775 -- If the following step fails, it's probably because there are still some non-null percent values in
7776 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7777 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7778 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7779 -- slipped in afterwards.
7780
7781 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7782 -- procedure acq.apply_percents() as defined above.
7783
7784 ALTER TABLE acq.fund_allocation
7785 ALTER COLUMN amount SET NOT NULL;
7786
7787 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7788     SELECT  fund,
7789             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7790     FROM acq.fund_allocation a
7791          JOIN acq.fund f ON (a.fund = f.id)
7792          JOIN acq.funding_source s ON (a.funding_source = s.id)
7793     GROUP BY 1;
7794
7795 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7796     SELECT  funding_source,
7797             SUM(a.amount)::NUMERIC(100,2) AS amount
7798     FROM  acq.fund_allocation a
7799     GROUP BY 1;
7800
7801 ALTER TABLE acq.fund_allocation
7802 DROP COLUMN percent;
7803
7804 CREATE TABLE asset.copy_location_order
7805 (
7806         id              SERIAL           PRIMARY KEY,
7807         location        INT              NOT NULL
7808                                              REFERENCES asset.copy_location
7809                                              ON DELETE CASCADE
7810                                              DEFERRABLE INITIALLY DEFERRED,
7811         org             INT              NOT NULL
7812                                              REFERENCES actor.org_unit
7813                                              ON DELETE CASCADE
7814                                              DEFERRABLE INITIALLY DEFERRED,
7815         position        INT              NOT NULL DEFAULT 0,
7816         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7817 );
7818
7819 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7820
7821 -- If you ran this before its most recent incarnation:
7822 -- delete from config.upgrade_log where version = '0328';
7823 -- alter table money.credit_card_payment drop column cc_name;
7824
7825 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7826 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7827
7828 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$
7829 DECLARE
7830     current_group    permission.grp_tree%ROWTYPE;
7831     user_object    actor.usr%ROWTYPE;
7832     item_object    asset.copy%ROWTYPE;
7833     cn_object    asset.call_number%ROWTYPE;
7834     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7835     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7836     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7837 BEGIN
7838     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7839     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7840     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7841     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7842     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7843
7844     LOOP 
7845         -- for each potential matchpoint for this ou and group ...
7846         FOR current_mp IN
7847             SELECT  m.*
7848               FROM  config.circ_matrix_matchpoint m
7849                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
7850                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
7851               WHERE m.grp = current_group.id
7852                     AND m.active
7853                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
7854                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
7855               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
7856                     CASE WHEN m.copy_owning_lib IS NOT NULL
7857                         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 )
7858                         ELSE 0
7859                     END +
7860                     CASE WHEN m.copy_circ_lib IS NOT NULL
7861                         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 )
7862                         ELSE 0
7863                     END +
7864                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
7865                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
7866                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
7867                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
7868                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
7869                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
7870                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
7871                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
7872                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
7873
7874             IF current_mp.is_renewal IS NOT NULL THEN
7875                 CONTINUE WHEN current_mp.is_renewal <> renewal;
7876             END IF;
7877
7878             IF current_mp.circ_modifier IS NOT NULL THEN
7879                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
7880             END IF;
7881
7882             IF current_mp.marc_type IS NOT NULL THEN
7883                 IF item_object.circ_as_type IS NOT NULL THEN
7884                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
7885                 ELSE
7886                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
7887                 END IF;
7888             END IF;
7889
7890             IF current_mp.marc_form IS NOT NULL THEN
7891                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
7892             END IF;
7893
7894             IF current_mp.marc_vr_format IS NOT NULL THEN
7895                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
7896             END IF;
7897
7898             IF current_mp.ref_flag IS NOT NULL THEN
7899                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
7900             END IF;
7901
7902             IF current_mp.juvenile_flag IS NOT NULL THEN
7903                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
7904             END IF;
7905
7906             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
7907                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
7908             END IF;
7909
7910             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
7911                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
7912             END IF;
7913
7914
7915             -- everything was undefined or matched
7916             matchpoint = current_mp;
7917
7918             EXIT WHEN matchpoint.id IS NOT NULL;
7919         END LOOP;
7920
7921         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
7922
7923         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
7924     END LOOP;
7925
7926     RETURN matchpoint;
7927 END;
7928 $func$ LANGUAGE plpgsql;
7929
7930 CREATE TYPE action.hold_stats AS (
7931     hold_count              INT,
7932     copy_count              INT,
7933     available_count         INT,
7934     total_copy_ratio        FLOAT,
7935     available_copy_ratio    FLOAT
7936 );
7937
7938 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
7939 DECLARE
7940     output          action.hold_stats%ROWTYPE;
7941     hold_count      INT := 0;
7942     copy_count      INT := 0;
7943     available_count INT := 0;
7944     hold_map_data   RECORD;
7945 BEGIN
7946
7947     output.hold_count := 0;
7948     output.copy_count := 0;
7949     output.available_count := 0;
7950
7951     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
7952       FROM  action.hold_copy_map m
7953             JOIN action.hold_request h ON (m.hold = h.id)
7954       WHERE m.target_copy = copy_id
7955             AND NOT h.frozen;
7956
7957     output.hold_count := hold_count;
7958
7959     IF output.hold_count > 0 THEN
7960         FOR hold_map_data IN
7961             SELECT  DISTINCT m.target_copy,
7962                     acp.status
7963               FROM  action.hold_copy_map m
7964                     JOIN asset.copy acp ON (m.target_copy = acp.id)
7965                     JOIN action.hold_request h ON (m.hold = h.id)
7966               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
7967         LOOP
7968             output.copy_count := output.copy_count + 1;
7969             IF hold_map_data.status IN (0,7,12) THEN
7970                 output.available_count := output.available_count + 1;
7971             END IF;
7972         END LOOP;
7973         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
7974         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
7975
7976     END IF;
7977
7978     RETURN output;
7979
7980 END;
7981 $func$ LANGUAGE PLPGSQL;
7982
7983 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
7984 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
7985
7986 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
7987
7988 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7989 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7990
7991 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
7992     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
7993     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
7994     copy_owning_lib
7995 );
7996
7997 -- Return the correct fail_part when the item can't be found
7998 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$
7999 DECLARE
8000     user_object        actor.usr%ROWTYPE;
8001     standing_penalty    config.standing_penalty%ROWTYPE;
8002     item_object        asset.copy%ROWTYPE;
8003     item_status_object    config.copy_status%ROWTYPE;
8004     item_location_object    asset.copy_location%ROWTYPE;
8005     result            action.matrix_test_result;
8006     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
8007     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
8008     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
8009     hold_ratio          action.hold_stats%ROWTYPE;
8010     penalty_type         TEXT;
8011     tmp_grp         INT;
8012     items_out        INT;
8013     context_org_list        INT[];
8014     done            BOOL := FALSE;
8015 BEGIN
8016     result.success := TRUE;
8017
8018     -- Fail if the user is BARRED
8019     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8020
8021     -- Fail if we couldn't find the user 
8022     IF user_object.id IS NULL THEN
8023         result.fail_part := 'no_user';
8024         result.success := FALSE;
8025         done := TRUE;
8026         RETURN NEXT result;
8027         RETURN;
8028     END IF;
8029
8030     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
8031
8032     -- Fail if we couldn't find the item 
8033     IF item_object.id IS NULL THEN
8034         result.fail_part := 'no_item';
8035         result.success := FALSE;
8036         done := TRUE;
8037         RETURN NEXT result;
8038         RETURN;
8039     END IF;
8040
8041     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
8042     result.matchpoint := circ_test.id;
8043
8044     -- Fail if we couldn't find a matchpoint
8045     IF result.matchpoint IS NULL THEN
8046         result.fail_part := 'no_matchpoint';
8047         result.success := FALSE;
8048         done := TRUE;
8049         RETURN NEXT result;
8050     END IF;
8051
8052     IF user_object.barred IS TRUE THEN
8053         result.fail_part := 'actor.usr.barred';
8054         result.success := FALSE;
8055         done := TRUE;
8056         RETURN NEXT result;
8057     END IF;
8058
8059     -- Fail if the item can't circulate
8060     IF item_object.circulate IS FALSE THEN
8061         result.fail_part := 'asset.copy.circulate';
8062         result.success := FALSE;
8063         done := TRUE;
8064         RETURN NEXT result;
8065     END IF;
8066
8067     -- Fail if the item isn't in a circulateable status on a non-renewal
8068     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
8069         result.fail_part := 'asset.copy.status';
8070         result.success := FALSE;
8071         done := TRUE;
8072         RETURN NEXT result;
8073     ELSIF renewal AND item_object.status <> 1 THEN
8074         result.fail_part := 'asset.copy.status';
8075         result.success := FALSE;
8076         done := TRUE;
8077         RETURN NEXT result;
8078     END IF;
8079
8080     -- Fail if the item can't circulate because of the shelving location
8081     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8082     IF item_location_object.circulate IS FALSE THEN
8083         result.fail_part := 'asset.copy_location.circulate';
8084         result.success := FALSE;
8085         done := TRUE;
8086         RETURN NEXT result;
8087     END IF;
8088
8089     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
8090
8091     -- Fail if the test is set to hard non-circulating
8092     IF circ_test.circulate IS FALSE THEN
8093         result.fail_part := 'config.circ_matrix_test.circulate';
8094         result.success := FALSE;
8095         done := TRUE;
8096         RETURN NEXT result;
8097     END IF;
8098
8099     -- Fail if the total copy-hold ratio is too low
8100     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
8101         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8102         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
8103             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8104             result.success := FALSE;
8105             done := TRUE;
8106             RETURN NEXT result;
8107         END IF;
8108     END IF;
8109
8110     -- Fail if the available copy-hold ratio is too low
8111     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
8112         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8113         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
8114             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8115             result.success := FALSE;
8116             done := TRUE;
8117             RETURN NEXT result;
8118         END IF;
8119     END IF;
8120
8121     IF renewal THEN
8122         penalty_type = '%RENEW%';
8123     ELSE
8124         penalty_type = '%CIRC%';
8125     END IF;
8126
8127     FOR standing_penalty IN
8128         SELECT  DISTINCT csp.*
8129           FROM  actor.usr_standing_penalty usp
8130                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8131           WHERE usr = match_user
8132                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
8133                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8134                 AND csp.block_list LIKE penalty_type LOOP
8135
8136         result.fail_part := standing_penalty.name;
8137         result.success := FALSE;
8138         done := TRUE;
8139         RETURN NEXT result;
8140     END LOOP;
8141
8142     -- Fail if the user has too many items with specific circ_modifiers checked out
8143     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
8144         SELECT  INTO items_out COUNT(*)
8145           FROM  action.circulation circ
8146             JOIN asset.copy cp ON (cp.id = circ.target_copy)
8147           WHERE circ.usr = match_user
8148                AND circ.circ_lib IN ( SELECT * FROM explode_array(context_org_list) )
8149             AND circ.checkin_time IS NULL
8150             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8151             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);
8152         IF items_out >= out_by_circ_mod.items_out THEN
8153             result.fail_part := 'config.circ_matrix_circ_mod_test';
8154             result.success := FALSE;
8155             done := TRUE;
8156             RETURN NEXT result;
8157         END IF;
8158     END LOOP;
8159
8160     -- If we passed everything, return the successful matchpoint id
8161     IF NOT done THEN
8162         RETURN NEXT result;
8163     END IF;
8164
8165     RETURN;
8166 END;
8167 $func$ LANGUAGE plpgsql;
8168
8169 CREATE TABLE config.remote_account (
8170     id          SERIAL  PRIMARY KEY,
8171     label       TEXT    NOT NULL,
8172     host        TEXT    NOT NULL,   -- name or IP, :port optional
8173     username    TEXT,               -- optional, since we could default to $USER
8174     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
8175     account     TEXT,               -- aka profile or FTP "account" command
8176     path        TEXT,               -- aka directory
8177     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8178     last_activity TIMESTAMP WITH TIME ZONE
8179 );
8180
8181 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8182     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8183     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8184         vendcode    TEXT,
8185         vendacct    TEXT
8186
8187 ) INHERITS (config.remote_account);
8188
8189 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8190
8191 CREATE TABLE acq.claim_type (
8192         id             SERIAL           PRIMARY KEY,
8193         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8194                                                  DEFERRABLE INITIALLY DEFERRED,
8195         code           TEXT             NOT NULL,
8196         description    TEXT             NOT NULL,
8197         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8198 );
8199
8200 CREATE TABLE acq.claim (
8201         id             SERIAL           PRIMARY KEY,
8202         type           INT              NOT NULL REFERENCES acq.claim_type
8203                                                  DEFERRABLE INITIALLY DEFERRED,
8204         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8205                                                  DEFERRABLE INITIALLY DEFERRED
8206 );
8207
8208 CREATE TABLE acq.claim_policy (
8209         id              SERIAL       PRIMARY KEY,
8210         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8211                                      DEFERRABLE INITIALLY DEFERRED,
8212         name            TEXT         NOT NULL,
8213         description     TEXT         NOT NULL,
8214         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8215 );
8216
8217 -- Add a san column for EDI. 
8218 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8219
8220 ALTER TABLE acq.provider ADD COLUMN san INT;
8221
8222 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8223
8224 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8225 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8226
8227 ALTER TABLE acq.provider
8228         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8229
8230 ALTER TABLE acq.provider
8231         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8232
8233 ALTER TABLE acq.provider
8234         ADD COLUMN url TEXT;
8235
8236 ALTER TABLE acq.provider
8237         ADD COLUMN email TEXT;
8238
8239 ALTER TABLE acq.provider
8240         ADD COLUMN phone TEXT;
8241
8242 ALTER TABLE acq.provider
8243         ADD COLUMN fax_phone TEXT;
8244
8245 ALTER TABLE acq.provider
8246         ADD COLUMN default_claim_policy INT
8247                 REFERENCES acq.claim_policy
8248                 DEFERRABLE INITIALLY DEFERRED;
8249
8250 ALTER TABLE action.transit_copy
8251 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8252                                                          DEFERRABLE INITIALLY DEFERRED;
8253
8254 DROP SCHEMA IF EXISTS booking CASCADE;
8255
8256 CREATE SCHEMA booking;
8257
8258 CREATE TABLE booking.resource_type (
8259         id             SERIAL          PRIMARY KEY,
8260         name           TEXT            NOT NULL,
8261         fine_interval  INTERVAL,
8262         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
8263         owner          INT             NOT NULL
8264                                        REFERENCES actor.org_unit( id )
8265                                        DEFERRABLE INITIALLY DEFERRED,
8266         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
8267         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
8268     record         BIGINT          REFERENCES biblio.record_entry (id)
8269                                        DEFERRABLE INITIALLY DEFERRED,
8270     max_fine       NUMERIC(8,2),
8271     elbow_room     INTERVAL,
8272     CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
8273 );
8274
8275 CREATE TABLE booking.resource (
8276         id             SERIAL           PRIMARY KEY,
8277         owner          INT              NOT NULL
8278                                         REFERENCES actor.org_unit(id)
8279                                         DEFERRABLE INITIALLY DEFERRED,
8280         type           INT              NOT NULL
8281                                         REFERENCES booking.resource_type(id)
8282                                         DEFERRABLE INITIALLY DEFERRED,
8283         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
8284         barcode        TEXT             NOT NULL,
8285         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
8286         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8287         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8288         CONSTRAINT br_unique UNIQUE (owner, barcode)
8289 );
8290
8291 -- For non-catalog items: hijack barcode for name/description
8292
8293 CREATE TABLE booking.resource_attr (
8294         id              SERIAL          PRIMARY KEY,
8295         owner           INT             NOT NULL
8296                                         REFERENCES actor.org_unit(id)
8297                                         DEFERRABLE INITIALLY DEFERRED,
8298         name            TEXT            NOT NULL,
8299         resource_type   INT             NOT NULL
8300                                         REFERENCES booking.resource_type(id)
8301                                         ON DELETE CASCADE
8302                                         DEFERRABLE INITIALLY DEFERRED,
8303         required        BOOLEAN         NOT NULL DEFAULT FALSE,
8304         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
8305 );
8306
8307 CREATE TABLE booking.resource_attr_value (
8308         id               SERIAL         PRIMARY KEY,
8309         owner            INT            NOT NULL
8310                                         REFERENCES actor.org_unit(id)
8311                                         DEFERRABLE INITIALLY DEFERRED,
8312         attr             INT            NOT NULL
8313                                         REFERENCES booking.resource_attr(id)
8314                                         DEFERRABLE INITIALLY DEFERRED,
8315         valid_value      TEXT           NOT NULL,
8316         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
8317 );
8318
8319 CREATE TABLE booking.resource_attr_map (
8320         id               SERIAL         PRIMARY KEY,
8321         resource         INT            NOT NULL
8322                                         REFERENCES booking.resource(id)
8323                                         ON DELETE CASCADE
8324                                         DEFERRABLE INITIALLY DEFERRED,
8325         resource_attr    INT            NOT NULL
8326                                         REFERENCES booking.resource_attr(id)
8327                                         ON DELETE CASCADE
8328                                         DEFERRABLE INITIALLY DEFERRED,
8329         value            INT            NOT NULL
8330                                         REFERENCES booking.resource_attr_value(id)
8331                                         DEFERRABLE INITIALLY DEFERRED,
8332         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
8333 );
8334
8335 CREATE TABLE booking.reservation (
8336         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
8337         start_time       TIMESTAMPTZ,
8338         end_time         TIMESTAMPTZ,
8339         capture_time     TIMESTAMPTZ,
8340         cancel_time      TIMESTAMPTZ,
8341         pickup_time      TIMESTAMPTZ,
8342         return_time      TIMESTAMPTZ,
8343         booking_interval INTERVAL,
8344         fine_interval    INTERVAL,
8345         fine_amount      DECIMAL(8,2),
8346         target_resource_type  INT       NOT NULL
8347                                         REFERENCES booking.resource_type(id)
8348                                         ON DELETE CASCADE
8349                                         DEFERRABLE INITIALLY DEFERRED,
8350         target_resource  INT            REFERENCES booking.resource(id)
8351                                         ON DELETE CASCADE
8352                                         DEFERRABLE INITIALLY DEFERRED,
8353         current_resource INT            REFERENCES booking.resource(id)
8354                                         ON DELETE CASCADE
8355                                         DEFERRABLE INITIALLY DEFERRED,
8356         request_lib      INT            NOT NULL
8357                                         REFERENCES actor.org_unit(id)
8358                                         DEFERRABLE INITIALLY DEFERRED,
8359         pickup_lib       INT            REFERENCES actor.org_unit(id)
8360                                         DEFERRABLE INITIALLY DEFERRED,
8361         capture_staff    INT            REFERENCES actor.usr(id)
8362                                         DEFERRABLE INITIALLY DEFERRED,
8363     max_fine         NUMERIC(8,2)
8364 ) INHERITS (money.billable_xact);
8365
8366 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
8367
8368 ALTER TABLE booking.reservation
8369         ADD CONSTRAINT booking_reservation_usr_fkey
8370         FOREIGN KEY (usr) REFERENCES actor.usr (id)
8371         DEFERRABLE INITIALLY DEFERRED;
8372
8373 CREATE TABLE booking.reservation_attr_value_map (
8374         id               SERIAL         PRIMARY KEY,
8375         reservation      INT            NOT NULL
8376                                         REFERENCES booking.reservation(id)
8377                                         ON DELETE CASCADE
8378                                         DEFERRABLE INITIALLY DEFERRED,
8379         attr_value       INT            NOT NULL
8380                                         REFERENCES booking.resource_attr_value(id)
8381                                         ON DELETE CASCADE
8382                                         DEFERRABLE INITIALLY DEFERRED,
8383         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
8384 );
8385
8386 -- represents a circ chain summary
8387 CREATE TYPE action.circ_chain_summary AS (
8388     num_circs INTEGER,
8389     start_time TIMESTAMP WITH TIME ZONE,
8390     checkout_workstation TEXT,
8391     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8392     last_stop_fines TEXT,
8393     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8394     last_renewal_workstation TEXT, -- NULL if no renewals
8395     last_checkin_workstation TEXT,
8396     last_checkin_time TIMESTAMP WITH TIME ZONE,
8397     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8398 );
8399
8400 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8401 DECLARE
8402     tmp_circ action.circulation%ROWTYPE;
8403     circ_0 action.circulation%ROWTYPE;
8404 BEGIN
8405
8406     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8407
8408     IF tmp_circ IS NULL THEN
8409         RETURN NEXT tmp_circ;
8410     END IF;
8411     circ_0 := tmp_circ;
8412
8413     -- find the front of the chain
8414     WHILE TRUE LOOP
8415         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8416         IF tmp_circ IS NULL THEN
8417             EXIT;
8418         END IF;
8419         circ_0 := tmp_circ;
8420     END LOOP;
8421
8422     -- now send the circs to the caller, oldest to newest
8423     tmp_circ := circ_0;
8424     WHILE TRUE LOOP
8425         IF tmp_circ IS NULL THEN
8426             EXIT;
8427         END IF;
8428         RETURN NEXT tmp_circ;
8429         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8430     END LOOP;
8431
8432 END;
8433 $$ LANGUAGE 'plpgsql';
8434
8435 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8436
8437 DECLARE
8438
8439     -- first circ in the chain
8440     circ_0 action.circulation%ROWTYPE;
8441
8442     -- last circ in the chain
8443     circ_n action.circulation%ROWTYPE;
8444
8445     -- circ chain under construction
8446     chain action.circ_chain_summary;
8447     tmp_circ action.circulation%ROWTYPE;
8448
8449 BEGIN
8450     
8451     chain.num_circs := 0;
8452     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8453
8454         IF chain.num_circs = 0 THEN
8455             circ_0 := tmp_circ;
8456         END IF;
8457
8458         chain.num_circs := chain.num_circs + 1;
8459         circ_n := tmp_circ;
8460     END LOOP;
8461
8462     chain.start_time := circ_0.xact_start;
8463     chain.last_stop_fines := circ_n.stop_fines;
8464     chain.last_stop_fines_time := circ_n.stop_fines_time;
8465     chain.last_checkin_time := circ_n.checkin_time;
8466     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8467     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8468     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8469
8470     IF chain.num_circs > 1 THEN
8471         chain.last_renewal_time := circ_n.xact_start;
8472         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8473     END IF;
8474
8475     RETURN chain;
8476
8477 END;
8478 $$ LANGUAGE 'plpgsql';
8479
8480 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8481 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8482 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8483
8484 ALTER TABLE config.standing_penalty
8485         ADD COLUMN org_depth   INTEGER;
8486
8487 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8488 DECLARE
8489     user_object         actor.usr%ROWTYPE;
8490     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8491     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8492     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8493     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8494     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8495     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8496     tmp_grp             INT;
8497     items_overdue       INT;
8498     items_out           INT;
8499     context_org_list    INT[];
8500     current_fines        NUMERIC(8,2) := 0.0;
8501     tmp_fines            NUMERIC(8,2);
8502     tmp_groc            RECORD;
8503     tmp_circ            RECORD;
8504     tmp_org             actor.org_unit%ROWTYPE;
8505     tmp_penalty         config.standing_penalty%ROWTYPE;
8506     tmp_depth           INTEGER;
8507 BEGIN
8508     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8509
8510     -- Max fines
8511     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8512
8513     -- Fail if the user has a high fine balance
8514     LOOP
8515         tmp_grp := user_object.profile;
8516         LOOP
8517             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8518
8519             IF max_fines.threshold IS NULL THEN
8520                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8521             ELSE
8522                 EXIT;
8523             END IF;
8524
8525             IF tmp_grp IS NULL THEN
8526                 EXIT;
8527             END IF;
8528         END LOOP;
8529
8530         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8531             EXIT;
8532         END IF;
8533
8534         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8535
8536     END LOOP;
8537
8538     IF max_fines.threshold IS NOT NULL THEN
8539
8540         FOR existing_sp_row IN
8541                 SELECT  *
8542                   FROM  actor.usr_standing_penalty
8543                   WHERE usr = match_user
8544                         AND org_unit = max_fines.org_unit
8545                         AND (stop_date IS NULL or stop_date > NOW())
8546                         AND standing_penalty = 1
8547                 LOOP
8548             RETURN NEXT existing_sp_row;
8549         END LOOP;
8550
8551         SELECT  SUM(f.balance_owed) INTO current_fines
8552           FROM  money.materialized_billable_xact_summary f
8553                 JOIN (
8554                     SELECT  r.id
8555                       FROM  booking.reservation r
8556                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8557                       WHERE usr = match_user
8558                             AND xact_finish IS NULL
8559                                 UNION ALL
8560                     SELECT  g.id
8561                       FROM  money.grocery g
8562                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8563                       WHERE usr = match_user
8564                             AND xact_finish IS NULL
8565                                 UNION ALL
8566                     SELECT  circ.id
8567                       FROM  action.circulation circ
8568                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8569                       WHERE usr = match_user
8570                             AND xact_finish IS NULL ) l USING (id);
8571
8572         IF current_fines >= max_fines.threshold THEN
8573             new_sp_row.usr := match_user;
8574             new_sp_row.org_unit := max_fines.org_unit;
8575             new_sp_row.standing_penalty := 1;
8576             RETURN NEXT new_sp_row;
8577         END IF;
8578     END IF;
8579
8580     -- Start over for max overdue
8581     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8582
8583     -- Fail if the user has too many overdue items
8584     LOOP
8585         tmp_grp := user_object.profile;
8586         LOOP
8587
8588             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8589
8590             IF max_overdue.threshold IS NULL THEN
8591                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8592             ELSE
8593                 EXIT;
8594             END IF;
8595
8596             IF tmp_grp IS NULL THEN
8597                 EXIT;
8598             END IF;
8599         END LOOP;
8600
8601         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8602             EXIT;
8603         END IF;
8604
8605         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8606
8607     END LOOP;
8608
8609     IF max_overdue.threshold IS NOT NULL THEN
8610
8611         FOR existing_sp_row IN
8612                 SELECT  *
8613                   FROM  actor.usr_standing_penalty
8614                   WHERE usr = match_user
8615                         AND org_unit = max_overdue.org_unit
8616                         AND (stop_date IS NULL or stop_date > NOW())
8617                         AND standing_penalty = 2
8618                 LOOP
8619             RETURN NEXT existing_sp_row;
8620         END LOOP;
8621
8622         SELECT  INTO items_overdue COUNT(*)
8623           FROM  action.circulation circ
8624                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8625           WHERE circ.usr = match_user
8626             AND circ.checkin_time IS NULL
8627             AND circ.due_date < NOW()
8628             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8629
8630         IF items_overdue >= max_overdue.threshold::INT THEN
8631             new_sp_row.usr := match_user;
8632             new_sp_row.org_unit := max_overdue.org_unit;
8633             new_sp_row.standing_penalty := 2;
8634             RETURN NEXT new_sp_row;
8635         END IF;
8636     END IF;
8637
8638     -- Start over for max out
8639     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8640
8641     -- Fail if the user has too many checked out items
8642     LOOP
8643         tmp_grp := user_object.profile;
8644         LOOP
8645             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8646
8647             IF max_items_out.threshold IS NULL THEN
8648                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8649             ELSE
8650                 EXIT;
8651             END IF;
8652
8653             IF tmp_grp IS NULL THEN
8654                 EXIT;
8655             END IF;
8656         END LOOP;
8657
8658         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8659             EXIT;
8660         END IF;
8661
8662         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8663
8664     END LOOP;
8665
8666
8667     -- Fail if the user has too many items checked out
8668     IF max_items_out.threshold IS NOT NULL THEN
8669
8670         FOR existing_sp_row IN
8671                 SELECT  *
8672                   FROM  actor.usr_standing_penalty
8673                   WHERE usr = match_user
8674                         AND org_unit = max_items_out.org_unit
8675                         AND (stop_date IS NULL or stop_date > NOW())
8676                         AND standing_penalty = 3
8677                 LOOP
8678             RETURN NEXT existing_sp_row;
8679         END LOOP;
8680
8681         SELECT  INTO items_out COUNT(*)
8682           FROM  action.circulation circ
8683                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8684           WHERE circ.usr = match_user
8685                 AND circ.checkin_time IS NULL
8686                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8687
8688            IF items_out >= max_items_out.threshold::INT THEN
8689             new_sp_row.usr := match_user;
8690             new_sp_row.org_unit := max_items_out.org_unit;
8691             new_sp_row.standing_penalty := 3;
8692             RETURN NEXT new_sp_row;
8693            END IF;
8694     END IF;
8695
8696     -- Start over for collections warning
8697     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8698
8699     -- Fail if the user has a collections-level fine balance
8700     LOOP
8701         tmp_grp := user_object.profile;
8702         LOOP
8703             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8704
8705             IF max_fines.threshold IS NULL THEN
8706                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8707             ELSE
8708                 EXIT;
8709             END IF;
8710
8711             IF tmp_grp IS NULL THEN
8712                 EXIT;
8713             END IF;
8714         END LOOP;
8715
8716         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8717             EXIT;
8718         END IF;
8719
8720         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8721
8722     END LOOP;
8723
8724     IF max_fines.threshold IS NOT NULL THEN
8725
8726         FOR existing_sp_row IN
8727                 SELECT  *
8728                   FROM  actor.usr_standing_penalty
8729                   WHERE usr = match_user
8730                         AND org_unit = max_fines.org_unit
8731                         AND (stop_date IS NULL or stop_date > NOW())
8732                         AND standing_penalty = 4
8733                 LOOP
8734             RETURN NEXT existing_sp_row;
8735         END LOOP;
8736
8737         SELECT  SUM(f.balance_owed) INTO current_fines
8738           FROM  money.materialized_billable_xact_summary f
8739                 JOIN (
8740                     SELECT  r.id
8741                       FROM  booking.reservation r
8742                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8743                       WHERE usr = match_user
8744                             AND xact_finish IS NULL
8745                                 UNION ALL
8746                     SELECT  g.id
8747                       FROM  money.grocery g
8748                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8749                       WHERE usr = match_user
8750                             AND xact_finish IS NULL
8751                                 UNION ALL
8752                     SELECT  circ.id
8753                       FROM  action.circulation circ
8754                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8755                       WHERE usr = match_user
8756                             AND xact_finish IS NULL ) l USING (id);
8757
8758         IF current_fines >= max_fines.threshold THEN
8759             new_sp_row.usr := match_user;
8760             new_sp_row.org_unit := max_fines.org_unit;
8761             new_sp_row.standing_penalty := 4;
8762             RETURN NEXT new_sp_row;
8763         END IF;
8764     END IF;
8765
8766     -- Start over for in collections
8767     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8768
8769     -- Remove the in-collections penalty if the user has paid down enough
8770     -- This penalty is different, because this code is not responsible for creating 
8771     -- new in-collections penalties, only for removing them
8772     LOOP
8773         tmp_grp := user_object.profile;
8774         LOOP
8775             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8776
8777             IF max_fines.threshold IS NULL THEN
8778                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8779             ELSE
8780                 EXIT;
8781             END IF;
8782
8783             IF tmp_grp IS NULL THEN
8784                 EXIT;
8785             END IF;
8786         END LOOP;
8787
8788         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8789             EXIT;
8790         END IF;
8791
8792         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8793
8794     END LOOP;
8795
8796     IF max_fines.threshold IS NOT NULL THEN
8797
8798         -- first, see if the user had paid down to the threshold
8799         SELECT  SUM(f.balance_owed) INTO current_fines
8800           FROM  money.materialized_billable_xact_summary f
8801                 JOIN (
8802                     SELECT  r.id
8803                       FROM  booking.reservation r
8804                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
8805                       WHERE usr = match_user
8806                             AND xact_finish IS NULL
8807                                 UNION ALL
8808                     SELECT  g.id
8809                       FROM  money.grocery g
8810                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
8811                       WHERE usr = match_user
8812                             AND xact_finish IS NULL
8813                                 UNION ALL
8814                     SELECT  circ.id
8815                       FROM  action.circulation circ
8816                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
8817                       WHERE usr = match_user
8818                             AND xact_finish IS NULL ) l USING (id);
8819
8820         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8821             -- patron has paid down enough
8822
8823             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8824
8825             IF tmp_penalty.org_depth IS NOT NULL THEN
8826
8827                 -- since this code is not responsible for applying the penalty, it can't 
8828                 -- guarantee the current context org will match the org at which the penalty 
8829                 --- was applied.  search up the org tree until we hit the configured penalty depth
8830                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8831                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8832
8833                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8834
8835                     FOR existing_sp_row IN
8836                             SELECT  *
8837                             FROM  actor.usr_standing_penalty
8838                             WHERE usr = match_user
8839                                     AND org_unit = tmp_org.id
8840                                     AND (stop_date IS NULL or stop_date > NOW())
8841                                     AND standing_penalty = 30 
8842                             LOOP
8843
8844                         -- Penalty exists, return it for removal
8845                         RETURN NEXT existing_sp_row;
8846                     END LOOP;
8847
8848                     IF tmp_org.parent_ou IS NULL THEN
8849                         EXIT;
8850                     END IF;
8851
8852                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8853                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8854                 END LOOP;
8855
8856             ELSE
8857
8858                 -- no penalty depth is defined, look for exact matches
8859
8860                 FOR existing_sp_row IN
8861                         SELECT  *
8862                         FROM  actor.usr_standing_penalty
8863                         WHERE usr = match_user
8864                                 AND org_unit = max_fines.org_unit
8865                                 AND (stop_date IS NULL or stop_date > NOW())
8866                                 AND standing_penalty = 30 
8867                         LOOP
8868                     -- Penalty exists, return it for removal
8869                     RETURN NEXT existing_sp_row;
8870                 END LOOP;
8871             END IF;
8872     
8873         END IF;
8874
8875     END IF;
8876
8877     RETURN;
8878 END;
8879 $func$ LANGUAGE plpgsql;
8880
8881 -- Create a default row in acq.fiscal_calendar
8882 -- Add a column in actor.org_unit to point to it
8883
8884 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8885
8886 ALTER TABLE actor.org_unit
8887 ADD COLUMN fiscal_calendar INT NOT NULL
8888         REFERENCES acq.fiscal_calendar( id )
8889         DEFERRABLE INITIALLY DEFERRED
8890         DEFAULT 1;
8891
8892 ALTER TABLE auditor.actor_org_unit_history
8893         ADD COLUMN fiscal_calendar INT;
8894
8895 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8896
8897 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8898
8899 ALTER TABLE acq.funding_source_credit
8900 ADD COLUMN deadline_date TIMESTAMPTZ;
8901
8902 ALTER TABLE acq.funding_source_credit
8903 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8904
8905 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8906
8907 CREATE TABLE acq.fund_transfer (
8908         id               SERIAL         PRIMARY KEY,
8909         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8910                                         DEFERRABLE INITIALLY DEFERRED,
8911         src_amount       NUMERIC        NOT NULL,
8912         dest_fund        INT            REFERENCES acq.fund( id )
8913                                         DEFERRABLE INITIALLY DEFERRED,
8914         dest_amount      NUMERIC,
8915         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
8916         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
8917                                         DEFERRABLE INITIALLY DEFERRED,
8918         note             TEXT,
8919     funding_source_credit INTEGER   NOT NULL
8920                                         REFERENCES acq.funding_source_credit(id)
8921                                         DEFERRABLE INITIALLY DEFERRED
8922 );
8923
8924 CREATE INDEX acqftr_usr_idx
8925 ON acq.fund_transfer( transfer_user );
8926
8927 COMMENT ON TABLE acq.fund_transfer IS $$
8928 /*
8929  * Copyright (C) 2009  Georgia Public Library Service
8930  * Scott McKellar <scott@esilibrary.com>
8931  *
8932  * Fund Transfer
8933  *
8934  * Each row represents the transfer of money from a source fund
8935  * to a destination fund.  There should be corresponding entries
8936  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
8937  * to record how much money moved from which fund to which other
8938  * fund.
8939  * 
8940  * The presence of two amount fields, rather than one, reflects
8941  * the possibility that the two funds are denominated in different
8942  * currencies.  If they use the same currency type, the two
8943  * amounts should be the same.
8944  *
8945  * ****
8946  *
8947  * This program is free software; you can redistribute it and/or
8948  * modify it under the terms of the GNU General Public License
8949  * as published by the Free Software Foundation; either version 2
8950  * of the License, or (at your option) any later version.
8951  *
8952  * This program is distributed in the hope that it will be useful,
8953  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8954  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
8955  * GNU General Public License for more details.
8956  */
8957 $$;
8958
8959 CREATE TABLE acq.claim_event_type (
8960         id             SERIAL           PRIMARY KEY,
8961         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8962                                                  DEFERRABLE INITIALLY DEFERRED,
8963         code           TEXT             NOT NULL,
8964         description    TEXT             NOT NULL,
8965         library_initiated BOOL          NOT NULL DEFAULT FALSE,
8966         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
8967 );
8968
8969 CREATE TABLE acq.claim_event (
8970         id             BIGSERIAL        PRIMARY KEY,
8971         type           INT              NOT NULL REFERENCES acq.claim_event_type
8972                                                  DEFERRABLE INITIALLY DEFERRED,
8973         claim          SERIAL           NOT NULL REFERENCES acq.claim
8974                                                  DEFERRABLE INITIALLY DEFERRED,
8975         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
8976         creator        INT              NOT NULL REFERENCES actor.usr
8977                                                  DEFERRABLE INITIALLY DEFERRED,
8978         note           TEXT
8979 );
8980
8981 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
8982
8983 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
8984         src_usr  IN INTEGER,
8985         dest_usr IN INTEGER
8986 ) RETURNS VOID AS $$
8987 DECLARE
8988         suffix TEXT;
8989         renamable_row RECORD;
8990 BEGIN
8991
8992         UPDATE actor.usr SET
8993                 active = FALSE,
8994                 card = NULL,
8995                 mailing_address = NULL,
8996                 billing_address = NULL
8997         WHERE id = src_usr;
8998
8999         -- acq.*
9000         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
9001         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
9002         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
9003         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
9004         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
9005         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
9006         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
9007
9008         -- Update with a rename to avoid collisions
9009         FOR renamable_row in
9010                 SELECT id, name
9011                 FROM   acq.picklist
9012                 WHERE  owner = src_usr
9013         LOOP
9014                 suffix := ' (' || src_usr || ')';
9015                 LOOP
9016                         BEGIN
9017                                 UPDATE  acq.picklist
9018                                 SET     owner = dest_usr, name = name || suffix
9019                                 WHERE   id = renamable_row.id;
9020                         EXCEPTION WHEN unique_violation THEN
9021                                 suffix := suffix || ' ';
9022                                 CONTINUE;
9023                         END;
9024                         EXIT;
9025                 END LOOP;
9026         END LOOP;
9027
9028         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
9029         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
9030         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
9031         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
9032         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
9033         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
9034         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
9035         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
9036
9037         -- action.*
9038         DELETE FROM action.circulation WHERE usr = src_usr;
9039         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
9040         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
9041         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
9042         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
9043         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
9044         DELETE FROM action.hold_request WHERE usr = src_usr;
9045         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
9046         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
9047         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
9048         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
9049         DELETE FROM action.survey_response WHERE usr = src_usr;
9050         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
9051
9052         -- actor.*
9053         DELETE FROM actor.card WHERE usr = src_usr;
9054         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
9055
9056         -- The following update is intended to avoid transient violations of a foreign
9057         -- key constraint, whereby actor.usr_address references itself.  It may not be
9058         -- necessary, but it does no harm.
9059         UPDATE actor.usr_address SET replaces = NULL
9060                 WHERE usr = src_usr AND replaces IS NOT NULL;
9061         DELETE FROM actor.usr_address WHERE usr = src_usr;
9062         DELETE FROM actor.usr_note WHERE usr = src_usr;
9063         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
9064         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
9065         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
9066         DELETE FROM actor.usr_setting WHERE usr = src_usr;
9067         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
9068         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
9069
9070         -- asset.*
9071         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
9072         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
9073         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
9074         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
9075         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
9076         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
9077
9078         -- auditor.*
9079         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
9080         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
9081         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
9082         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
9083         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
9084         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
9085         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
9086         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
9087
9088         -- biblio.*
9089         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
9090         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
9091         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
9092         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
9093
9094         -- container.*
9095         -- Update buckets with a rename to avoid collisions
9096         FOR renamable_row in
9097                 SELECT id, name
9098                 FROM   container.biblio_record_entry_bucket
9099                 WHERE  owner = src_usr
9100         LOOP
9101                 suffix := ' (' || src_usr || ')';
9102                 LOOP
9103                         BEGIN
9104                                 UPDATE  container.biblio_record_entry_bucket
9105                                 SET     owner = dest_usr, name = name || suffix
9106                                 WHERE   id = renamable_row.id;
9107                         EXCEPTION WHEN unique_violation THEN
9108                                 suffix := suffix || ' ';
9109                                 CONTINUE;
9110                         END;
9111                         EXIT;
9112                 END LOOP;
9113         END LOOP;
9114
9115         FOR renamable_row in
9116                 SELECT id, name
9117                 FROM   container.call_number_bucket
9118                 WHERE  owner = src_usr
9119         LOOP
9120                 suffix := ' (' || src_usr || ')';
9121                 LOOP
9122                         BEGIN
9123                                 UPDATE  container.call_number_bucket
9124                                 SET     owner = dest_usr, name = name || suffix
9125                                 WHERE   id = renamable_row.id;
9126                         EXCEPTION WHEN unique_violation THEN
9127                                 suffix := suffix || ' ';
9128                                 CONTINUE;
9129                         END;
9130                         EXIT;
9131                 END LOOP;
9132         END LOOP;
9133
9134         FOR renamable_row in
9135                 SELECT id, name
9136                 FROM   container.copy_bucket
9137                 WHERE  owner = src_usr
9138         LOOP
9139                 suffix := ' (' || src_usr || ')';
9140                 LOOP
9141                         BEGIN
9142                                 UPDATE  container.copy_bucket
9143                                 SET     owner = dest_usr, name = name || suffix
9144                                 WHERE   id = renamable_row.id;
9145                         EXCEPTION WHEN unique_violation THEN
9146                                 suffix := suffix || ' ';
9147                                 CONTINUE;
9148                         END;
9149                         EXIT;
9150                 END LOOP;
9151         END LOOP;
9152
9153         FOR renamable_row in
9154                 SELECT id, name
9155                 FROM   container.user_bucket
9156                 WHERE  owner = src_usr
9157         LOOP
9158                 suffix := ' (' || src_usr || ')';
9159                 LOOP
9160                         BEGIN
9161                                 UPDATE  container.user_bucket
9162                                 SET     owner = dest_usr, name = name || suffix
9163                                 WHERE   id = renamable_row.id;
9164                         EXCEPTION WHEN unique_violation THEN
9165                                 suffix := suffix || ' ';
9166                                 CONTINUE;
9167                         END;
9168                         EXIT;
9169                 END LOOP;
9170         END LOOP;
9171
9172         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
9173
9174         -- money.*
9175         DELETE FROM money.billable_xact WHERE usr = src_usr;
9176         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9177         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9178
9179         -- permission.*
9180         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9181         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9182         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9183         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9184
9185         -- reporter.*
9186         -- Update with a rename to avoid collisions
9187         BEGIN
9188                 FOR renamable_row in
9189                         SELECT id, name
9190                         FROM   reporter.output_folder
9191                         WHERE  owner = src_usr
9192                 LOOP
9193                         suffix := ' (' || src_usr || ')';
9194                         LOOP
9195                                 BEGIN
9196                                         UPDATE  reporter.output_folder
9197                                         SET     owner = dest_usr, name = name || suffix
9198                                         WHERE   id = renamable_row.id;
9199                                 EXCEPTION WHEN unique_violation THEN
9200                                         suffix := suffix || ' ';
9201                                         CONTINUE;
9202                                 END;
9203                                 EXIT;
9204                         END LOOP;
9205                 END LOOP;
9206         EXCEPTION WHEN undefined_table THEN
9207                 -- do nothing
9208         END;
9209
9210         BEGIN
9211                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9212         EXCEPTION WHEN undefined_table THEN
9213                 -- do nothing
9214         END;
9215
9216         -- Update with a rename to avoid collisions
9217         BEGIN
9218                 FOR renamable_row in
9219                         SELECT id, name
9220                         FROM   reporter.report_folder
9221                         WHERE  owner = src_usr
9222                 LOOP
9223                         suffix := ' (' || src_usr || ')';
9224                         LOOP
9225                                 BEGIN
9226                                         UPDATE  reporter.report_folder
9227                                         SET     owner = dest_usr, name = name || suffix
9228                                         WHERE   id = renamable_row.id;
9229                                 EXCEPTION WHEN unique_violation THEN
9230                                         suffix := suffix || ' ';
9231                                         CONTINUE;
9232                                 END;
9233                                 EXIT;
9234                         END LOOP;
9235                 END LOOP;
9236         EXCEPTION WHEN undefined_table THEN
9237                 -- do nothing
9238         END;
9239
9240         BEGIN
9241                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9242         EXCEPTION WHEN undefined_table THEN
9243                 -- do nothing
9244         END;
9245
9246         BEGIN
9247                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9248         EXCEPTION WHEN undefined_table THEN
9249                 -- do nothing
9250         END;
9251
9252         -- Update with a rename to avoid collisions
9253         BEGIN
9254                 FOR renamable_row in
9255                         SELECT id, name
9256                         FROM   reporter.template_folder
9257                         WHERE  owner = src_usr
9258                 LOOP
9259                         suffix := ' (' || src_usr || ')';
9260                         LOOP
9261                                 BEGIN
9262                                         UPDATE  reporter.template_folder
9263                                         SET     owner = dest_usr, name = name || suffix
9264                                         WHERE   id = renamable_row.id;
9265                                 EXCEPTION WHEN unique_violation THEN
9266                                         suffix := suffix || ' ';
9267                                         CONTINUE;
9268                                 END;
9269                                 EXIT;
9270                         END LOOP;
9271                 END LOOP;
9272         EXCEPTION WHEN undefined_table THEN
9273         -- do nothing
9274         END;
9275
9276         -- vandelay.*
9277         -- Update with a rename to avoid collisions
9278         FOR renamable_row in
9279                 SELECT id, name
9280                 FROM   vandelay.queue
9281                 WHERE  owner = src_usr
9282         LOOP
9283                 suffix := ' (' || src_usr || ')';
9284                 LOOP
9285                         BEGIN
9286                                 UPDATE  vandelay.queue
9287                                 SET     owner = dest_usr, name = name || suffix
9288                                 WHERE   id = renamable_row.id;
9289                         EXCEPTION WHEN unique_violation THEN
9290                                 suffix := suffix || ' ';
9291                                 CONTINUE;
9292                         END;
9293                         EXIT;
9294                 END LOOP;
9295         END LOOP;
9296
9297 END;
9298 $$ LANGUAGE plpgsql;
9299
9300 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9301 /**
9302  * Finds rows dependent on a given row in actor.usr and either deletes them
9303  * or reassigns them to a different user.
9304  */
9305 $$;
9306
9307 CREATE OR REPLACE FUNCTION actor.usr_delete(
9308         src_usr  IN INTEGER,
9309         dest_usr IN INTEGER
9310 ) RETURNS VOID AS $$
9311 DECLARE
9312         old_profile actor.usr.profile%type;
9313         old_home_ou actor.usr.home_ou%type;
9314         new_profile actor.usr.profile%type;
9315         new_home_ou actor.usr.home_ou%type;
9316         new_name    text;
9317         new_dob     actor.usr.dob%type;
9318 BEGIN
9319         SELECT
9320                 id || '-PURGED-' || now(),
9321                 profile,
9322                 home_ou,
9323                 dob
9324         INTO
9325                 new_name,
9326                 old_profile,
9327                 old_home_ou,
9328                 new_dob
9329         FROM
9330                 actor.usr
9331         WHERE
9332                 id = src_usr;
9333         --
9334         -- Quit if no such user
9335         --
9336         IF old_profile IS NULL THEN
9337                 RETURN;
9338         END IF;
9339         --
9340         perform actor.usr_purge_data( src_usr, dest_usr );
9341         --
9342         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9343         -- could assume that there is only one root.  Theoretically, someday, maybe,
9344         -- there could be multiple roots, so we take extra trouble to get the right ones.
9345         --
9346         SELECT
9347                 id
9348         INTO
9349                 new_profile
9350         FROM
9351                 permission.grp_ancestors( old_profile )
9352         WHERE
9353                 parent is null;
9354         --
9355         SELECT
9356                 id
9357         INTO
9358                 new_home_ou
9359         FROM
9360                 actor.org_unit_ancestors( old_home_ou )
9361         WHERE
9362                 parent_ou is null;
9363         --
9364         -- Truncate date of birth
9365         --
9366         IF new_dob IS NOT NULL THEN
9367                 new_dob := date_trunc( 'year', new_dob );
9368         END IF;
9369         --
9370         UPDATE
9371                 actor.usr
9372                 SET
9373                         card = NULL,
9374                         profile = new_profile,
9375                         usrname = new_name,
9376                         email = NULL,
9377                         passwd = random()::text,
9378                         standing = DEFAULT,
9379                         ident_type = 
9380                         (
9381                                 SELECT MIN( id )
9382                                 FROM config.identification_type
9383                         ),
9384                         ident_value = NULL,
9385                         ident_type2 = NULL,
9386                         ident_value2 = NULL,
9387                         net_access_level = DEFAULT,
9388                         photo_url = NULL,
9389                         prefix = NULL,
9390                         first_given_name = new_name,
9391                         second_given_name = NULL,
9392                         family_name = new_name,
9393                         suffix = NULL,
9394                         alias = NULL,
9395                         day_phone = NULL,
9396                         evening_phone = NULL,
9397                         other_phone = NULL,
9398                         mailing_address = NULL,
9399                         billing_address = NULL,
9400                         home_ou = new_home_ou,
9401                         dob = new_dob,
9402                         active = FALSE,
9403                         master_account = DEFAULT, 
9404                         super_user = DEFAULT,
9405                         barred = FALSE,
9406                         deleted = TRUE,
9407                         juvenile = DEFAULT,
9408                         usrgroup = 0,
9409                         claims_returned_count = DEFAULT,
9410                         credit_forward_balance = DEFAULT,
9411                         last_xact_id = DEFAULT,
9412                         alert_message = NULL,
9413                         create_date = now(),
9414                         expire_date = now()
9415         WHERE
9416                 id = src_usr;
9417 END;
9418 $$ LANGUAGE plpgsql;
9419
9420 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9421 /**
9422  * Logically deletes a user.  Removes personally identifiable information,
9423  * and purges associated data in other tables.
9424  */
9425 $$;
9426
9427 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9428
9429 ALTER TABLE acq.fund
9430 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9431
9432 ALTER TABLE acq.fund
9433         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9434
9435 -- A fund can't roll over if it doesn't propagate from one year to the next
9436
9437 ALTER TABLE acq.fund
9438         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9439         ( propagate OR NOT rollover );
9440
9441 ALTER TABLE acq.fund
9442         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9443
9444 ALTER TABLE acq.fund
9445     ADD COLUMN balance_warning_percent INT
9446     CONSTRAINT balance_warning_percent_limit
9447         CHECK( balance_warning_percent <= 100 );
9448
9449 ALTER TABLE acq.fund
9450     ADD COLUMN balance_stop_percent INT
9451     CONSTRAINT balance_stop_percent_limit
9452         CHECK( balance_stop_percent <= 100 );
9453
9454 CREATE VIEW acq.ordered_funding_source_credit AS
9455         SELECT
9456                 CASE WHEN deadline_date IS NULL THEN
9457                         2
9458                 ELSE
9459                         1
9460                 END AS sort_priority,
9461                 CASE WHEN deadline_date IS NULL THEN
9462                         effective_date
9463                 ELSE
9464                         deadline_date
9465                 END AS sort_date,
9466                 id,
9467                 funding_source,
9468                 amount,
9469                 note
9470         FROM
9471                 acq.funding_source_credit;
9472
9473 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9474 /*
9475  * Copyright (C) 2009  Georgia Public Library Service
9476  * Scott McKellar <scott@gmail.com>
9477  *
9478  * The acq.ordered_funding_source_credit view is a prioritized
9479  * ordering of funding source credits.  When ordered by the first
9480  * three columns, this view defines the order in which the various
9481  * credits are to be tapped for spending, subject to the allocations
9482  * in the acq.fund_allocation table.
9483  *
9484  * The first column reflects the principle that we should spend
9485  * money with deadlines before spending money without deadlines.
9486  *
9487  * The second column reflects the principle that we should spend the
9488  * oldest money first.  For money with deadlines, that means that we
9489  * spend first from the credit with the earliest deadline.  For
9490  * money without deadlines, we spend first from the credit with the
9491  * earliest effective date.  
9492  *
9493  * The third column is a tie breaker to ensure a consistent
9494  * ordering.
9495  *
9496  * ****
9497  *
9498  * This program is free software; you can redistribute it and/or
9499  * modify it under the terms of the GNU General Public License
9500  * as published by the Free Software Foundation; either version 2
9501  * of the License, or (at your option) any later version.
9502  *
9503  * This program is distributed in the hope that it will be useful,
9504  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9505  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9506  * GNU General Public License for more details.
9507  */
9508 $$;
9509
9510 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9511     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9512       FROM  money.materialized_billable_xact_summary m
9513             LEFT JOIN action.circulation c ON (c.id = m.id)
9514             LEFT JOIN money.grocery g ON (g.id = m.id)
9515             LEFT JOIN booking.reservation r ON (r.id = m.id);
9516
9517 CREATE TABLE config.marc21_rec_type_map (
9518     code        TEXT    PRIMARY KEY,
9519     type_val    TEXT    NOT NULL,
9520     blvl_val    TEXT    NOT NULL
9521 );
9522
9523 CREATE TABLE config.marc21_ff_pos_map (
9524     id          SERIAL  PRIMARY KEY,
9525     fixed_field TEXT    NOT NULL,
9526     tag         TEXT    NOT NULL,
9527     rec_type    TEXT    NOT NULL,
9528     start_pos   INT     NOT NULL,
9529     length      INT     NOT NULL,
9530     default_val TEXT    NOT NULL DEFAULT ' '
9531 );
9532
9533 CREATE TABLE config.marc21_physical_characteristic_type_map (
9534     ptype_key   TEXT    PRIMARY KEY,
9535     label       TEXT    NOT NULL -- I18N
9536 );
9537
9538 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9539     id          SERIAL  PRIMARY KEY,
9540     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9541     subfield    TEXT    NOT NULL,
9542     start_pos   INT     NOT NULL,
9543     length      INT     NOT NULL,
9544     label       TEXT    NOT NULL -- I18N
9545 );
9546
9547 CREATE TABLE config.marc21_physical_characteristic_value_map (
9548     id              SERIAL  PRIMARY KEY,
9549     value           TEXT    NOT NULL,
9550     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9551     label           TEXT    NOT NULL -- I18N
9552 );
9553
9554 ----------------------------------
9555 -- MARC21 record structure data --
9556 ----------------------------------
9557
9558 -- Record type map
9559 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9560 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9561 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9562 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9563 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9564 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9565 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9566 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9567
9568 ------ Physical Characteristics
9569
9570 -- Map
9571 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9572 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9573 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9574 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9575 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9576 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9577 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9578 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');
9579 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9580 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9581 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9582 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9583 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9584 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');
9585 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9586 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9587 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9588 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9589 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9590 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9591 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9592 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9593 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9594 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9595 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');
9596 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');
9597 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');
9598 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');
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_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other photographic medium');
9601 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9602 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9603 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9604 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');
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 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9607 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
9608 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');
9609 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9610 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');
9611 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
9612 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9613 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9614 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9615 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9616 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9617 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9618 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');
9619
9620 -- Electronic Resource
9621 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9622 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9623 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');
9624 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');
9625 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');
9626 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');
9627 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');
9628 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');
9629 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');
9630 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');
9631 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9632 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9633 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9634 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
9635 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');
9636 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');
9637 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9638 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');
9639 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9640 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');
9641 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9642 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9643 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9644 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.');
9645 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.');
9646 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.');
9647 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.');
9648 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.');
9649 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');
9650 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.');
9651 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9652 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.');
9653 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9654 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
9655 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)');
9656 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
9657 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9658 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9659 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9660 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9661 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');
9662 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9663 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');
9664 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');
9665 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9666 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9667 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9668 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');
9669 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
9670 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9671 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
9672 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');
9673 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');
9674 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');
9675 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)');
9676 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9677 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');
9678 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9679 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9680 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9681 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9682 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9683 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9684 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9685 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
9686 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9687 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');
9688 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9689 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9690 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9691
9692 -- Globe
9693 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9694 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9695 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');
9696 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');
9697 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');
9698 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');
9699 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9700 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9701 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
9702 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');
9703 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9704 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9705 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9706 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9707 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9708 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9709 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9710 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9711 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9712 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9713 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9714 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9715 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9716 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9717 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');
9718 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9719 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9720
9721 -- Tactile Material
9722 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9723 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9724 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9725 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9726 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9727 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');
9728 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9729 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9730 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9731 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');
9732 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');
9733 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');
9734 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');
9735 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');
9736 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');
9737 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');
9738 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9739 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9740 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9741 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9742 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9743 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9744 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');
9745 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9746 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9747 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9748 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');
9749 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');
9750 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');
9751 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9752 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');
9753 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');
9754 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');
9755 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');
9756 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');
9757 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');
9758 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9759 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');
9760 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9761 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9762 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9763 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
9764 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');
9765 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');
9766 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');
9767 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
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
9770 -- Projected Graphic
9771 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9772 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9773 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');
9774 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9775 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');
9776 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');
9777 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9778 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
9779 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9780 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9781 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');
9782 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9783 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');
9784 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9785 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');
9786 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9787 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9788 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9789 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9790 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9791 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');
9792 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');
9793 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');
9794 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9795 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9796 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9797 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');
9798 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');
9799 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');
9800 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9801 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9802 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');
9803 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');
9804 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');
9805 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');
9806 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');
9807 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');
9808 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');
9809 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9810 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9811 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9812 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9813 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9814 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.');
9815 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.');
9816 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.');
9817 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.');
9818 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.');
9819 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.');
9820 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.');
9821 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.)');
9822 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.)');
9823 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.)');
9824 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.)');
9825 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9826 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.)');
9827 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.)');
9828 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.)');
9829 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.)');
9830 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9831 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9832 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9833 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9834 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9835 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9836 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');
9837 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');
9838 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');
9839 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9840 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9841
9842 -- Microform
9843 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9844 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9845 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');
9846 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');
9847 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');
9848 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');
9849 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9850 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');
9851 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9853 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9854 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9855 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9856 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9857 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9858 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9859 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
9860 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.');
9861 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.');
9862 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.');
9863 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9864 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.');
9865 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.)');
9866 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.)');
9867 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.)');
9868 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.)');
9869 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9870 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9871 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','f','5','4','Reduction ratio range/Reduction ratio');
9872 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)');
9873 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)');
9874 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)');
9875 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)');
9876 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-)');
9877 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9878 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');
9879 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9880 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');
9881 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9882 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9883 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9884 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9885 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9886 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');
9887 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9888 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9889 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9890 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');
9891 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9892 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9893 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
9894 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');
9895 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');
9896 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');
9897 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');
9898 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9899 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9900 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');
9901 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');
9902 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');
9903 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');
9904 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');
9905 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9906 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Safety base, polyester');
9907 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');
9908 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');
9909 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9910 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9911
9912 -- Non-projected Graphic
9913 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9914 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
9915 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
9916 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
9917 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
9918 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');
9919 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
9920 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
9921 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
9922 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
9923 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');
9924 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
9925 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');
9926 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9927 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9928 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
9929 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');
9930 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');
9931 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9932 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');
9933 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9934 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9935 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9936 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
9937 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9938 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');
9939 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');
9940 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9941 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9942 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9943 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9944 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9945 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');
9946 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9947 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9948 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9949 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9950 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9951 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9952 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9953 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9954 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
9955 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9956 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');
9957 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');
9958 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9959 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9960 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9961 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9962 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9963 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9964 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9965 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9966 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9967 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9968 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9969 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9970 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9971 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9972
9973 -- Motion Picture
9974 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
9975 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
9976 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');
9977 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');
9978 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');
9979 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9980 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9981 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
9982 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');
9983 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9984 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');
9985 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
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_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9988 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
9989 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');
9990 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)');
9991 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
9992 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)');
9993 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');
9994 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');
9995 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9996 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9997 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');
9998 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');
9999 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');
10000 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10001 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
10002 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');
10003 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');
10004 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');
10005 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');
10006 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');
10007 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');
10008 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');
10009 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10010 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
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 ('m','h','7','1','Dimensions');
10014 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.');
10015 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.');
10016 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.');
10017 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.');
10018 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.');
10019 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.');
10020 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.');
10021 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10022 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10023 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
10024 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10025 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10026 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');
10027 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');
10028 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10029 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10030 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10031 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
10032 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');
10033 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
10034 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
10035 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
10036 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');
10037 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');
10038 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');
10039 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');
10040 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10041
10042 -- Remote-sensing Image
10043 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
10044 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
10045 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10046 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
10047 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
10048 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
10049 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
10050 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');
10051 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10052 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10053 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
10054 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');
10055 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');
10056 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
10057 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');
10058 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10059 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
10060 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%');
10061 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%');
10062 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%');
10063 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%');
10064 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%');
10065 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%');
10066 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%');
10067 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%');
10068 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%');
10069 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%');
10070 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10071 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10072 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
10073 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
10074 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');
10075 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');
10076 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');
10077 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');
10078 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');
10079 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');
10080 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');
10081 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');
10082 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10083 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10084 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10085 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
10086 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
10087 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');
10088 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');
10089 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');
10090 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');
10091 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10092 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10093 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
10094 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
10095 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
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 ('r','j','9','2','Data type');
10099 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');
10100 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');
10101 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');
10102 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');
10103 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');
10104 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)');
10105 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');
10106 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
10107 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');
10108 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)');
10109 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)');
10110 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)');
10111 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');
10112 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');
10113 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');
10114 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');
10115 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');
10116 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');
10117 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');
10118 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');
10119 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');
10120 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');
10121 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');
10122 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');
10123 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');
10124 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');
10125 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');
10126 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');
10127 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');
10128 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');
10129 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');
10130 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');
10131 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');
10132 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)');
10133 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');
10134 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
10135 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
10136 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');
10137 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');
10138 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10139 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10140
10141 -- Sound Recording
10142 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
10143 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
10144 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');
10145 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
10146 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');
10147 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');
10148 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
10149 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');
10150 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');
10151 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10152 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');
10153 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10154 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
10155 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');
10156 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');
10157 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');
10158 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');
10159 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');
10160 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');
10161 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');
10162 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');
10163 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');
10164 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');
10165 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');
10166 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');
10167 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');
10168 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');
10169 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10170 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10171 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
10172 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10173 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
10174 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10175 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10176 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10177 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10178 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');
10179 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');
10180 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');
10181 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10182 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10183 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10184 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.');
10185 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.');
10186 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.');
10187 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.');
10188 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.');
10189 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.');
10190 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.)');
10191 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.');
10192 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');
10193 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.');
10194 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.');
10195 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10196 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10197 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
10198 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.');
10199 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.');
10200 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');
10201 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.');
10202 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.');
10203 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10204 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10205 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
10206 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');
10207 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');
10208 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');
10209 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');
10210 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');
10211 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');
10212 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');
10213 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10214 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10215 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10216 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');
10217 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');
10218 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');
10219 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');
10220 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');
10221 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');
10222 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');
10223 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');
10224 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');
10225 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10226 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10227 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
10228 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');
10229 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');
10230 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');
10231 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');
10232 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10233 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10234
10235 -- Videorecording
10236 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10237 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10238 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10239 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10240 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10241 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10242 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10243 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10244 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10245 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');
10246 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10247 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10248 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');
10249 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10250 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10251 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10252 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10253 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10254 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');
10255 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10256 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');
10257 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10258 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10259 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10260 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10261 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');
10262 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');
10263 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');
10264 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');
10265 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.');
10266 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.');
10267 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10268 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10269 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10270 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');
10271 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');
10272 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');
10273 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10274 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10275 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');
10276 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');
10277 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');
10278 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');
10279 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');
10280 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');
10281 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');
10282 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10283 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10284 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10285 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10286 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10287 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.');
10288 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.');
10289 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.');
10290 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.');
10291 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.');
10292 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.');
10293 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10294 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10295 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10296 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10297 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10298 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10299 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multichannel, surround or quadraphonic');
10300 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10301 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10302 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10303
10304 -- Fixed Field position data -- 0-based!
10305 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10306 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10307 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10308 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10309 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10310 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10311 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10312 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10313 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10314 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10315 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10316 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10317 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10318 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10319 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10320 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10321 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10322 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10323 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10324 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10325 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10326 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10327 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10328 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10329 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10330 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10331 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10332 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10333 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10334 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10335 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10336 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10337 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10338 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10339 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10340 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10341 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10342 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10343 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10344 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10345 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10346 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10347 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10348 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10349 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10350 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10351 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10352 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10353 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10354 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10355 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10356 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10357 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10358 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10359 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10360 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10361 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10362 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10363 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10364 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10365 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10366 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10367 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10368 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10369 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10370 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10371 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10372 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10373 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10374 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10375 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10376 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10377 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10378 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10379 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10380 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10381 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10382 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10383 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10384 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10385 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10386 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10387 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10388 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10389 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10390 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10391 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10392 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10393 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10394 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10395 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10396 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10397 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10398 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10399 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10400 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10401 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10402 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10403 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10404 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10405 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10406 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10407 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10408 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10409 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10410 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10411 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10412 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10413 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10414 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10415 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10416 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10417 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10418 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10419 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10420 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10421 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10422 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10423 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10424 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10425 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10426 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10427 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10428 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10429 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10430 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10431 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10432 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10433 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10434 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10435 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10436 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10437 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10438 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10439 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');
10440 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');
10441 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10442 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10443 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10444 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10445 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10446 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10447 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10448 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10449 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10450 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10451
10452 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10453 DECLARE
10454         ldr         RECORD;
10455         tval        TEXT;
10456         tval_rec    RECORD;
10457         bval        TEXT;
10458         bval_rec    RECORD;
10459     retval      config.marc21_rec_type_map%ROWTYPE;
10460 BEGIN
10461     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10462
10463     IF ldr.id IS NULL THEN
10464         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10465         RETURN retval;
10466     END IF;
10467
10468     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10469     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10470
10471
10472     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10473     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10474
10475     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10476
10477     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10478
10479
10480     IF retval.code IS NULL THEN
10481         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10482     END IF;
10483
10484     RETURN retval;
10485 END;
10486 $func$ LANGUAGE PLPGSQL;
10487
10488 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10489 DECLARE
10490     rtype       TEXT;
10491     ff_pos      RECORD;
10492     tag_data    RECORD;
10493     val         TEXT;
10494 BEGIN
10495     rtype := (biblio.marc21_record_type( rid )).code;
10496     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10497         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10498             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10499             RETURN val;
10500         END LOOP;
10501         val := REPEAT( ff_pos.default_val, ff_pos.length );
10502         RETURN val;
10503     END LOOP;
10504
10505     RETURN NULL;
10506 END;
10507 $func$ LANGUAGE PLPGSQL;
10508
10509 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10510 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10511 DECLARE
10512     rowid   INT := 0;
10513     _007    RECORD;
10514     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10515     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10516     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10517     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10518 BEGIN
10519
10520     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10521
10522     IF _007.id IS NOT NULL THEN
10523         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10524
10525         IF ptype.ptype_key IS NOT NULL THEN
10526             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10527                 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 );
10528
10529                 IF pval.id IS NOT NULL THEN
10530                     rowid := rowid + 1;
10531                     retval.id := rowid;
10532                     retval.record := rid;
10533                     retval.ptype := ptype.ptype_key;
10534                     retval.subfield := psf.id;
10535                     retval.value := pval.id;
10536                     RETURN NEXT retval;
10537                 END IF;
10538
10539             END LOOP;
10540         END IF;
10541     END IF;
10542
10543     RETURN;
10544 END;
10545 $func$ LANGUAGE PLPGSQL;
10546
10547 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10548 DROP VIEW IF EXISTS money.open_usr_summary;
10549 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10550
10551 -- The view should supply defaults for numeric (amount) columns
10552 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10553     SELECT  xact.id,
10554         xact.usr,
10555         xact.xact_start,
10556         xact.xact_finish,
10557         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10558         credit.payment_ts AS last_payment_ts,
10559         credit.note AS last_payment_note,
10560         credit.payment_type AS last_payment_type,
10561         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10562         debit.billing_ts AS last_billing_ts,
10563         debit.note AS last_billing_note,
10564         debit.billing_type AS last_billing_type,
10565         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10566         p.relname AS xact_type
10567       FROM  money.billable_xact xact
10568         JOIN pg_class p ON xact.tableoid = p.oid
10569         LEFT JOIN (
10570             SELECT  billing.xact,
10571                 sum(billing.amount) AS amount,
10572                 max(billing.billing_ts) AS billing_ts,
10573                 last(billing.note) AS note,
10574                 last(billing.billing_type) AS billing_type
10575               FROM  money.billing
10576               WHERE billing.voided IS FALSE
10577               GROUP BY billing.xact
10578             ) debit ON xact.id = debit.xact
10579         LEFT JOIN (
10580             SELECT  payment_view.xact,
10581                 sum(payment_view.amount) AS amount,
10582                 max(payment_view.payment_ts) AS payment_ts,
10583                 last(payment_view.note) AS note,
10584                 last(payment_view.payment_type) AS payment_type
10585               FROM  money.payment_view
10586               WHERE payment_view.voided IS FALSE
10587               GROUP BY payment_view.xact
10588             ) credit ON xact.id = credit.xact
10589       ORDER BY debit.billing_ts, credit.payment_ts;
10590
10591 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10592     SELECT * FROM money.billable_xact_summary_location_view
10593     WHERE xact_finish IS NULL;
10594
10595 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10596     SELECT 
10597         usr,
10598         SUM(total_paid) AS total_paid,
10599         SUM(total_owed) AS total_owed,
10600         SUM(balance_owed) AS balance_owed
10601     FROM  money.materialized_billable_xact_summary
10602     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10603     GROUP BY usr;
10604
10605 CREATE OR REPLACE VIEW money.usr_summary AS
10606     SELECT 
10607         usr, 
10608         sum(total_paid) AS total_paid, 
10609         sum(total_owed) AS total_owed, 
10610         sum(balance_owed) AS balance_owed
10611     FROM money.materialized_billable_xact_summary
10612     GROUP BY usr;
10613
10614 CREATE OR REPLACE VIEW money.open_usr_summary AS
10615     SELECT 
10616         usr, 
10617         sum(total_paid) AS total_paid, 
10618         sum(total_owed) AS total_owed, 
10619         sum(balance_owed) AS balance_owed
10620     FROM money.materialized_billable_xact_summary
10621     WHERE xact_finish IS NULL
10622     GROUP BY usr;
10623
10624 -- 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;
10625
10626 CREATE TABLE config.biblio_fingerprint (
10627         id                      SERIAL  PRIMARY KEY,
10628         name            TEXT    NOT NULL, 
10629         xpath           TEXT    NOT NULL,
10630     first_word  BOOL    NOT NULL DEFAULT FALSE,
10631         format          TEXT    NOT NULL DEFAULT 'marcxml'
10632 );
10633
10634 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10635     VALUES (
10636         'Title',
10637         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10638             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10639             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10640             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10641             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10642         'marcxml'
10643     );
10644
10645 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10646     VALUES (
10647         'Author',
10648         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10649             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10650             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10651             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10652             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10653         'marcxml',
10654         TRUE
10655     );
10656
10657 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10658 DECLARE
10659     qual        INT;
10660     ldr         TEXT;
10661     tval        TEXT;
10662     tval_rec    RECORD;
10663     bval        TEXT;
10664     bval_rec    RECORD;
10665     type_map    RECORD;
10666     ff_pos      RECORD;
10667     ff_tag_data TEXT;
10668 BEGIN
10669
10670     IF marc IS NULL OR marc = '' THEN
10671         RETURN NULL;
10672     END IF;
10673
10674     -- First, the count of tags
10675     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10676
10677     -- now go through a bunch of pain to get the record type
10678     IF best_type IS NOT NULL THEN
10679         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10680
10681         IF ldr IS NOT NULL THEN
10682             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10683             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10684
10685
10686             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10687             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10688
10689             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10690
10691             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10692
10693             IF type_map.code IS NOT NULL THEN
10694                 IF best_type = type_map.code THEN
10695                     qual := qual + qual / 2;
10696                 END IF;
10697
10698                 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
10699                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10700                     IF ff_tag_data = best_lang THEN
10701                             qual := qual + 100;
10702                     END IF;
10703                 END LOOP;
10704             END IF;
10705         END IF;
10706     END IF;
10707
10708     -- Now look for some quality metrics
10709     -- DCL record?
10710     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10711         qual := qual + 10;
10712     END IF;
10713
10714     -- From OCLC?
10715     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10716         qual := qual + 10;
10717     END IF;
10718
10719     RETURN qual;
10720
10721 END;
10722 $func$ LANGUAGE PLPGSQL;
10723
10724 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10725 DECLARE
10726     idx     config.biblio_fingerprint%ROWTYPE;
10727     xfrm        config.xml_transform%ROWTYPE;
10728     prev_xfrm   TEXT;
10729     transformed_xml TEXT;
10730     xml_node    TEXT;
10731     xml_node_list   TEXT[];
10732     raw_text    TEXT;
10733     output_text TEXT := '';
10734 BEGIN
10735
10736     IF marc IS NULL OR marc = '' THEN
10737         RETURN NULL;
10738     END IF;
10739
10740     -- Loop over the indexing entries
10741     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10742
10743         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10744
10745         -- See if we can skip the XSLT ... it's expensive
10746         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10747             -- Can't skip the transform
10748             IF xfrm.xslt <> '---' THEN
10749                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10750             ELSE
10751                 transformed_xml := marc;
10752             END IF;
10753
10754             prev_xfrm := xfrm.name;
10755         END IF;
10756
10757         raw_text := COALESCE(
10758             naco_normalize(
10759                 ARRAY_TO_STRING(
10760                     oils_xpath(
10761                         '//text()',
10762                         (oils_xpath(
10763                             idx.xpath,
10764                             transformed_xml,
10765                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10766                         ))[1]
10767                     ),
10768                     ''
10769                 )
10770             ),
10771             ''
10772         );
10773
10774         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10775         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10776
10777         IF idx.first_word IS TRUE THEN
10778             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10779         END IF;
10780
10781         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10782
10783     END LOOP;
10784
10785     RETURN output_text;
10786
10787 END;
10788 $func$ LANGUAGE PLPGSQL;
10789
10790 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10791 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10792 BEGIN
10793
10794     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10795
10796     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10797         RETURN NEW;
10798     END IF;
10799
10800     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10801     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10802
10803     RETURN NEW;
10804
10805 END;
10806 $func$ LANGUAGE PLPGSQL;
10807
10808 CREATE TABLE config.internal_flag (
10809     name    TEXT    PRIMARY KEY,
10810     value   TEXT,
10811     enabled BOOL    NOT NULL DEFAULT FALSE
10812 );
10813 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10814 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10815 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10816 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10817 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10818 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10819 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10820 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10821 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10822 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10823
10824 CREATE TABLE authority.bib_linking (
10825     id          BIGSERIAL   PRIMARY KEY,
10826     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10827     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10828 );
10829 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10830 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10831
10832 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10833     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10834 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10835
10836 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10837     DELETE FROM authority.bib_linking WHERE bib = $1;
10838     INSERT INTO authority.bib_linking (bib, authority)
10839         SELECT  y.bib,
10840                 y.authority
10841           FROM (    SELECT  DISTINCT $1 AS bib,
10842                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10843                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10844                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10845                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10846     SELECT $1;
10847 $func$ LANGUAGE SQL;
10848
10849 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10850 BEGIN
10851     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10852     IF NOT FOUND THEN
10853         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10854     END IF;
10855     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)
10856         SELECT  bib_id,
10857                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10858                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10859                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10860                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10861                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10862                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10863                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10864                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10865                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10866                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10867                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10868                 (   SELECT  v.value
10869                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10870                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10871                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10872                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10873                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
10874                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
10875
10876     RETURN;
10877 END;
10878 $func$ LANGUAGE PLPGSQL;
10879
10880 CREATE TABLE config.metabib_class (
10881     name    TEXT    PRIMARY KEY,
10882     label   TEXT    NOT NULL UNIQUE
10883 );
10884
10885 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10886 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10887 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10888 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10889 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10890
10891 CREATE TABLE metabib.facet_entry (
10892         id              BIGSERIAL       PRIMARY KEY,
10893         source          BIGINT          NOT NULL,
10894         field           INT             NOT NULL,
10895         value           TEXT            NOT NULL
10896 );
10897
10898 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10899 DECLARE
10900     fclass          RECORD;
10901     ind_data        metabib.field_entry_template%ROWTYPE;
10902 BEGIN
10903     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10904     IF NOT FOUND THEN
10905         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10906             -- RAISE NOTICE 'Emptying out %', fclass.name;
10907             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10908         END LOOP;
10909         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10910     END IF;
10911
10912     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10913         IF ind_data.field < 0 THEN
10914             ind_data.field = -1 * ind_data.field;
10915             INSERT INTO metabib.facet_entry (field, source, value)
10916                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10917         ELSE
10918             EXECUTE $$
10919                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10920                     VALUES ($$ ||
10921                         quote_literal(ind_data.field) || $$, $$ ||
10922                         quote_literal(ind_data.source) || $$, $$ ||
10923                         quote_literal(ind_data.value) ||
10924                     $$);$$;
10925         END IF;
10926
10927     END LOOP;
10928
10929     RETURN;
10930 END;
10931 $func$ LANGUAGE PLPGSQL;
10932
10933 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
10934 DECLARE
10935     uris            TEXT[];
10936     uri_xml         TEXT;
10937     uri_label       TEXT;
10938     uri_href        TEXT;
10939     uri_use         TEXT;
10940     uri_owner       TEXT;
10941     uri_owner_id    INT;
10942     uri_id          INT;
10943     uri_cn_id       INT;
10944     uri_map_id      INT;
10945 BEGIN
10946
10947     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
10948     IF ARRAY_UPPER(uris,1) > 0 THEN
10949         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
10950             -- First we pull info out of the 856
10951             uri_xml     := uris[i];
10952
10953             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
10954             CONTINUE WHEN uri_href IS NULL;
10955
10956             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10957             CONTINUE WHEN uri_label IS NULL;
10958
10959             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
10960             CONTINUE WHEN uri_owner IS NULL;
10961
10962             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10963
10964             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
10965
10966             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
10967             CONTINUE WHEN NOT FOUND;
10968
10969             -- now we look for a matching uri
10970             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10971             IF NOT FOUND THEN -- create one
10972                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
10973                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10974             END IF;
10975
10976             -- we need a call number to link through
10977             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;
10978             IF NOT FOUND THEN
10979                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
10980                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
10981                 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;
10982             END IF;
10983
10984             -- now, link them if they're not already
10985             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
10986             IF NOT FOUND THEN
10987                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
10988             END IF;
10989
10990         END LOOP;
10991     END IF;
10992
10993     RETURN;
10994 END;
10995 $func$ LANGUAGE PLPGSQL;
10996
10997 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
10998 DECLARE
10999     source_count    INT;
11000     old_mr          BIGINT;
11001     tmp_mr          metabib.metarecord%ROWTYPE;
11002     deleted_mrs     BIGINT[];
11003 BEGIN
11004
11005     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
11006
11007     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
11008
11009         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
11010             old_mr := tmp_mr.id;
11011         ELSE
11012             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
11013             IF source_count = 0 THEN -- No other records
11014                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
11015                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
11016             END IF;
11017         END IF;
11018
11019     END LOOP;
11020
11021     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
11022         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
11023         IF old_mr IS NULL THEN -- nope, create one and grab its id
11024             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
11025             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
11026         ELSE -- indeed there is. update it with a null cache and recalcualated master record
11027             UPDATE  metabib.metarecord
11028               SET   mods = NULL,
11029                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11030               WHERE id = old_mr;
11031         END IF;
11032     ELSE -- there was one we already attached to, update its mods cache and master_record
11033         UPDATE  metabib.metarecord
11034           SET   mods = NULL,
11035                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11036           WHERE id = old_mr;
11037     END IF;
11038
11039     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
11040
11041     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
11042         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
11043     END IF;
11044
11045     RETURN old_mr;
11046
11047 END;
11048 $func$ LANGUAGE PLPGSQL;
11049
11050 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
11051 BEGIN
11052     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
11053     IF NOT FOUND THEN
11054         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
11055     END IF;
11056     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
11057         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
11058
11059     RETURN;
11060 END;
11061 $func$ LANGUAGE PLPGSQL;
11062
11063 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
11064 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
11065 BEGIN
11066
11067     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
11068         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
11069         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
11070         RETURN NEW; -- and we're done
11071     END IF;
11072
11073     IF TG_OP = 'UPDATE' THEN -- re-ingest?
11074         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
11075
11076         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
11077             RETURN NEW;
11078         END IF;
11079     END IF;
11080
11081     -- Record authority linking
11082     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
11083     IF NOT FOUND THEN
11084         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11085     END IF;
11086
11087     -- Flatten and insert the mfr data
11088     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11089     IF NOT FOUND THEN
11090         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11091         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11092         IF NOT FOUND THEN
11093             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
11094         END IF;
11095     END IF;
11096
11097     -- Gather and insert the field entry data
11098     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11099
11100     -- Located URI magic
11101     IF TG_OP = 'INSERT' THEN
11102         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11103         IF NOT FOUND THEN
11104             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11105         END IF;
11106     ELSE
11107         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11108         IF NOT FOUND THEN
11109             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11110         END IF;
11111     END IF;
11112
11113     -- (re)map metarecord-bib linking
11114     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11115         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11116         IF NOT FOUND THEN
11117             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11118         END IF;
11119     ELSE -- we're doing an update, and we're not deleted, remap
11120         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11121         IF NOT FOUND THEN
11122             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11123         END IF;
11124     END IF;
11125
11126     RETURN NEW;
11127 END;
11128 $func$ LANGUAGE PLPGSQL;
11129
11130 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
11131 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 ();
11132
11133 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
11134
11135 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
11136 DECLARE
11137     xpath_list  TEXT[];
11138     select_list TEXT[];
11139     where_list  TEXT[];
11140     q           TEXT;
11141     out_record  RECORD;
11142     empty_test  RECORD;
11143 BEGIN
11144     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
11145  
11146     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
11147  
11148     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
11149         IF xpath_list[i] = 'null()' THEN
11150             select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
11151         ELSE
11152             select_list := ARRAY_APPEND(
11153                 select_list,
11154                 $sel$
11155                 EXPLODE_ARRAY(
11156                     COALESCE(
11157                         NULLIF(
11158                             oils_xpath(
11159                                 $sel$ ||
11160                                     quote_literal(
11161                                         CASE
11162                                             WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
11163                                             ELSE xpath_list[i] || '//text()'
11164                                         END
11165                                     ) ||
11166                                 $sel$,
11167                                 $sel$ || document_field || $sel$
11168                             ),
11169                            '{}'::TEXT[]
11170                         ),
11171                         '{NULL}'::TEXT[]
11172                     )
11173                 ) AS c_$sel$ || i
11174             );
11175             where_list := ARRAY_APPEND(
11176                 where_list,
11177                 'c_' || i || ' IS NOT NULL'
11178             );
11179         END IF;
11180     END LOOP;
11181  
11182     q := $q$
11183 SELECT * FROM (
11184     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11185 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
11186     -- RAISE NOTICE 'query: %', q;
11187  
11188     FOR out_record IN EXECUTE q LOOP
11189         RETURN NEXT out_record;
11190     END LOOP;
11191  
11192     RETURN;
11193 END;
11194 $func$ LANGUAGE PLPGSQL IMMUTABLE;
11195
11196 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11197 DECLARE
11198
11199     owning_lib      TEXT;
11200     circ_lib        TEXT;
11201     call_number     TEXT;
11202     copy_number     TEXT;
11203     status          TEXT;
11204     location        TEXT;
11205     circulate       TEXT;
11206     deposit         TEXT;
11207     deposit_amount  TEXT;
11208     ref             TEXT;
11209     holdable        TEXT;
11210     price           TEXT;
11211     barcode         TEXT;
11212     circ_modifier   TEXT;
11213     circ_as_type    TEXT;
11214     alert_message   TEXT;
11215     opac_visible    TEXT;
11216     pub_note        TEXT;
11217     priv_note       TEXT;
11218
11219     attr_def        RECORD;
11220     tmp_attr_set    RECORD;
11221     attr_set        vandelay.import_item%ROWTYPE;
11222
11223     xpath           TEXT;
11224
11225 BEGIN
11226
11227     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11228
11229     IF FOUND THEN
11230
11231         attr_set.definition := attr_def.id; 
11232     
11233         -- Build the combined XPath
11234     
11235         owning_lib :=
11236             CASE
11237                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11238                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11239                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11240             END;
11241     
11242         circ_lib :=
11243             CASE
11244                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11245                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11246                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11247             END;
11248     
11249         call_number :=
11250             CASE
11251                 WHEN attr_def.call_number IS NULL THEN 'null()'
11252                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11253                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11254             END;
11255     
11256         copy_number :=
11257             CASE
11258                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11259                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11260                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11261             END;
11262     
11263         status :=
11264             CASE
11265                 WHEN attr_def.status IS NULL THEN 'null()'
11266                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11267                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11268             END;
11269     
11270         location :=
11271             CASE
11272                 WHEN attr_def.location IS NULL THEN 'null()'
11273                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11274                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11275             END;
11276     
11277         circulate :=
11278             CASE
11279                 WHEN attr_def.circulate IS NULL THEN 'null()'
11280                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11281                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11282             END;
11283     
11284         deposit :=
11285             CASE
11286                 WHEN attr_def.deposit IS NULL THEN 'null()'
11287                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11288                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11289             END;
11290     
11291         deposit_amount :=
11292             CASE
11293                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11294                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11295                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11296             END;
11297     
11298         ref :=
11299             CASE
11300                 WHEN attr_def.ref IS NULL THEN 'null()'
11301                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11302                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11303             END;
11304     
11305         holdable :=
11306             CASE
11307                 WHEN attr_def.holdable IS NULL THEN 'null()'
11308                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11309                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11310             END;
11311     
11312         price :=
11313             CASE
11314                 WHEN attr_def.price IS NULL THEN 'null()'
11315                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11316                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11317             END;
11318     
11319         barcode :=
11320             CASE
11321                 WHEN attr_def.barcode IS NULL THEN 'null()'
11322                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11323                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11324             END;
11325     
11326         circ_modifier :=
11327             CASE
11328                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11329                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11330                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11331             END;
11332     
11333         circ_as_type :=
11334             CASE
11335                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11336                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11337                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11338             END;
11339     
11340         alert_message :=
11341             CASE
11342                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11343                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11344                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11345             END;
11346     
11347         opac_visible :=
11348             CASE
11349                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11350                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11351                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11352             END;
11353
11354         pub_note :=
11355             CASE
11356                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11357                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11358                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11359             END;
11360         priv_note :=
11361             CASE
11362                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11363                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11364                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11365             END;
11366     
11367     
11368         xpath := 
11369             owning_lib      || '|' || 
11370             circ_lib        || '|' || 
11371             call_number     || '|' || 
11372             copy_number     || '|' || 
11373             status          || '|' || 
11374             location        || '|' || 
11375             circulate       || '|' || 
11376             deposit         || '|' || 
11377             deposit_amount  || '|' || 
11378             ref             || '|' || 
11379             holdable        || '|' || 
11380             price           || '|' || 
11381             barcode         || '|' || 
11382             circ_modifier   || '|' || 
11383             circ_as_type    || '|' || 
11384             alert_message   || '|' || 
11385             pub_note        || '|' || 
11386             priv_note       || '|' || 
11387             opac_visible;
11388
11389         -- RAISE NOTICE 'XPath: %', xpath;
11390         
11391         FOR tmp_attr_set IN
11392                 SELECT  *
11393                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11394                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11395                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11396                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11397         LOOP
11398     
11399             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11400             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11401
11402             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11403             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11404     
11405             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11406             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11407             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11408     
11409             SELECT  id INTO attr_set.location
11410               FROM  asset.copy_location
11411               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11412                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11413     
11414             attr_set.circulate      :=
11415                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11416                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11417
11418             attr_set.deposit        :=
11419                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11420                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11421
11422             attr_set.holdable       :=
11423                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11424                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11425
11426             attr_set.opac_visible   :=
11427                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11428                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11429
11430             attr_set.ref            :=
11431                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11432                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11433     
11434             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11435             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11436             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11437     
11438             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11439             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11440             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11441             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11442             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11443             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11444             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11445             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11446     
11447             RETURN NEXT attr_set;
11448     
11449         END LOOP;
11450     
11451     END IF;
11452
11453     RETURN;
11454
11455 END;
11456 $$ LANGUAGE PLPGSQL;
11457
11458 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11459 DECLARE
11460     attr_def    BIGINT;
11461     item_data   vandelay.import_item%ROWTYPE;
11462 BEGIN
11463
11464     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11465
11466     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11467         INSERT INTO vandelay.import_item (
11468             record,
11469             definition,
11470             owning_lib,
11471             circ_lib,
11472             call_number,
11473             copy_number,
11474             status,
11475             location,
11476             circulate,
11477             deposit,
11478             deposit_amount,
11479             ref,
11480             holdable,
11481             price,
11482             barcode,
11483             circ_modifier,
11484             circ_as_type,
11485             alert_message,
11486             pub_note,
11487             priv_note,
11488             opac_visible
11489         ) VALUES (
11490             NEW.id,
11491             item_data.definition,
11492             item_data.owning_lib,
11493             item_data.circ_lib,
11494             item_data.call_number,
11495             item_data.copy_number,
11496             item_data.status,
11497             item_data.location,
11498             item_data.circulate,
11499             item_data.deposit,
11500             item_data.deposit_amount,
11501             item_data.ref,
11502             item_data.holdable,
11503             item_data.price,
11504             item_data.barcode,
11505             item_data.circ_modifier,
11506             item_data.circ_as_type,
11507             item_data.alert_message,
11508             item_data.pub_note,
11509             item_data.priv_note,
11510             item_data.opac_visible
11511         );
11512     END LOOP;
11513
11514     RETURN NULL;
11515 END;
11516 $func$ LANGUAGE PLPGSQL;
11517
11518 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11519 BEGIN
11520     EXECUTE $$
11521         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11522     $$;
11523         RETURN TRUE;
11524 END;
11525 $creator$ LANGUAGE 'plpgsql';
11526
11527 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11528 BEGIN
11529     EXECUTE $$
11530         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11531             audit_id    BIGINT                          PRIMARY KEY,
11532             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11533             audit_action        TEXT                            NOT NULL,
11534             LIKE $$ || sch || $$.$$ || tbl || $$
11535         );
11536     $$;
11537         RETURN TRUE;
11538 END;
11539 $creator$ LANGUAGE 'plpgsql';
11540
11541 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11542 BEGIN
11543     EXECUTE $$
11544         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11545         RETURNS TRIGGER AS $func$
11546         BEGIN
11547             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11548                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11549                     now(),
11550                     SUBSTR(TG_OP,1,1),
11551                     OLD.*;
11552             RETURN NULL;
11553         END;
11554         $func$ LANGUAGE 'plpgsql';
11555     $$;
11556         RETURN TRUE;
11557 END;
11558 $creator$ LANGUAGE 'plpgsql';
11559
11560 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11561 BEGIN
11562     EXECUTE $$
11563         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11564             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11565             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11566     $$;
11567         RETURN TRUE;
11568 END;
11569 $creator$ LANGUAGE 'plpgsql';
11570
11571 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11572 BEGIN
11573     EXECUTE $$
11574         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11575             SELECT      -1, now() as audit_time, '-' as audit_action, *
11576               FROM      $$ || sch || $$.$$ || tbl || $$
11577                 UNION ALL
11578             SELECT      *
11579               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11580     $$;
11581         RETURN TRUE;
11582 END;
11583 $creator$ LANGUAGE 'plpgsql';
11584
11585 -- The main event
11586
11587 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11588 BEGIN
11589     PERFORM acq.create_acq_seq(sch, tbl);
11590     PERFORM acq.create_acq_history(sch, tbl);
11591     PERFORM acq.create_acq_func(sch, tbl);
11592     PERFORM acq.create_acq_update_trigger(sch, tbl);
11593     PERFORM acq.create_acq_lifecycle(sch, tbl);
11594     RETURN TRUE;
11595 END;
11596 $creator$ LANGUAGE 'plpgsql';
11597
11598 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11599
11600 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11601     SELECT  fund.id AS fund,
11602             fund_debit.encumbrance AS encumbrance,
11603             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11604       FROM acq.fund AS fund
11605                         LEFT JOIN acq.fund_debit AS fund_debit
11606                                 ON ( fund.id = fund_debit.fund )
11607       GROUP BY 1,2;
11608
11609 CREATE TABLE acq.debit_attribution (
11610         id                     INT         NOT NULL PRIMARY KEY,
11611         fund_debit             INT         NOT NULL
11612                                            REFERENCES acq.fund_debit
11613                                            DEFERRABLE INITIALLY DEFERRED,
11614     debit_amount           NUMERIC     NOT NULL,
11615         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11616                                            DEFERRABLE INITIALLY DEFERRED,
11617     credit_amount          NUMERIC
11618 );
11619
11620 CREATE INDEX acq_attribution_debit_idx
11621         ON acq.debit_attribution( fund_debit );
11622
11623 CREATE INDEX acq_attribution_credit_idx
11624         ON acq.debit_attribution( funding_source_credit );
11625
11626 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11627 /*
11628 Function to attribute expenditures and encumbrances to funding source credits,
11629 and thereby to funding sources.
11630
11631 Read the debits in chonological order, attributing each one to one or
11632 more funding source credits.  Constraints:
11633
11634 1. Don't attribute more to a credit than the amount of the credit.
11635
11636 2. For a given fund, don't attribute more to a funding source than the
11637 source has allocated to that fund.
11638
11639 3. Attribute debits to credits with deadlines before attributing them to
11640 credits without deadlines.  Otherwise attribute to the earliest credits
11641 first, based on the deadline date when present, or on the effective date
11642 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11643 This ordering is defined by an ORDER BY clause on the view
11644 acq.ordered_funding_source_credit.
11645
11646 Start by truncating the table acq.debit_attribution.  Then insert a row
11647 into that table for each attribution.  If a debit cannot be fully
11648 attributed, insert a row for the unattributable balance, with the 
11649 funding_source_credit and credit_amount columns NULL.
11650 */
11651 DECLARE
11652         curr_fund_source_bal RECORD;
11653         seqno                INT;     -- sequence num for credits applicable to a fund
11654         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11655         fc                   RECORD;  -- used for loading t_fund_credit table
11656         sc                   RECORD;  -- used for loading t_fund_credit table
11657         --
11658         -- Used exclusively in the main loop:
11659         --
11660         deb                 RECORD;   -- current row from acq.fund_debit table
11661         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11662         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11663         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11664         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11665         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11666         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11667         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11668         attrib_count        INT;      -- populates id of acq.debit_attribution
11669 BEGIN
11670         --
11671         -- Load a temporary table.  For each combination of fund and funding source,
11672         -- load an entry with the total amount allocated to that fund by that source.
11673         -- This sum may reflect transfers as well as original allocations.  We will
11674         -- reduce this balance whenever we attribute debits to it.
11675         --
11676         CREATE TEMP TABLE t_fund_source_bal
11677         ON COMMIT DROP AS
11678                 SELECT
11679                         fund AS fund,
11680                         funding_source AS source,
11681                         sum( amount ) AS balance
11682                 FROM
11683                         acq.fund_allocation
11684                 GROUP BY
11685                         fund,
11686                         funding_source
11687                 HAVING
11688                         sum( amount ) > 0;
11689         --
11690         CREATE INDEX t_fund_source_bal_idx
11691                 ON t_fund_source_bal( fund, source );
11692         -------------------------------------------------------------------------------
11693         --
11694         -- Load another temporary table.  For each fund, load zero or more
11695         -- funding source credits from which that fund can get money.
11696         --
11697         CREATE TEMP TABLE t_fund_credit (
11698                 fund        INT,
11699                 seq         INT,
11700                 credit      INT
11701         ) ON COMMIT DROP;
11702         --
11703         FOR fc IN
11704                 SELECT DISTINCT fund
11705                 FROM acq.fund_allocation
11706                 ORDER BY fund
11707         LOOP                  -- Loop over the funds
11708                 seqno := 1;
11709                 FOR sc IN
11710                         SELECT
11711                                 ofsc.id
11712                         FROM
11713                                 acq.ordered_funding_source_credit AS ofsc
11714                         WHERE
11715                                 ofsc.funding_source IN
11716                                 (
11717                                         SELECT funding_source
11718                                         FROM acq.fund_allocation
11719                                         WHERE fund = fc.fund
11720                                 )
11721                 ORDER BY
11722                     ofsc.sort_priority,
11723                     ofsc.sort_date,
11724                     ofsc.id
11725                 LOOP                        -- Add each credit to the list
11726                         INSERT INTO t_fund_credit (
11727                                 fund,
11728                                 seq,
11729                                 credit
11730                         ) VALUES (
11731                                 fc.fund,
11732                                 seqno,
11733                                 sc.id
11734                         );
11735                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11736                         seqno := seqno + 1;
11737                 END LOOP;     -- Loop over credits for a given fund
11738         END LOOP;         -- Loop over funds
11739         --
11740         CREATE INDEX t_fund_credit_idx
11741                 ON t_fund_credit( fund, seq );
11742         -------------------------------------------------------------------------------
11743         --
11744         -- Load yet another temporary table.  This one is a list of funding source
11745         -- credits, with their balances.  We shall reduce those balances as we
11746         -- attribute debits to them.
11747         --
11748         CREATE TEMP TABLE t_credit
11749         ON COMMIT DROP AS
11750         SELECT
11751             fsc.id AS credit,
11752             fsc.funding_source AS source,
11753             fsc.amount AS balance,
11754             fs.currency_type AS currency_type
11755         FROM
11756             acq.funding_source_credit AS fsc,
11757             acq.funding_source fs
11758         WHERE
11759             fsc.funding_source = fs.id
11760                         AND fsc.amount > 0;
11761         --
11762         CREATE INDEX t_credit_idx
11763                 ON t_credit( credit );
11764         --
11765         -------------------------------------------------------------------------------
11766         --
11767         -- Now that we have loaded the lookup tables: loop through the debits,
11768         -- attributing each one to one or more funding source credits.
11769         -- 
11770         truncate table acq.debit_attribution;
11771         --
11772         attrib_count := 0;
11773         FOR deb in
11774                 SELECT
11775                         fd.id,
11776                         fd.fund,
11777                         fd.amount,
11778                         f.currency_type,
11779                         fd.encumbrance
11780                 FROM
11781                         acq.fund_debit fd,
11782                         acq.fund f
11783                 WHERE
11784                         fd.fund = f.id
11785                 ORDER BY
11786                         fd.id
11787         LOOP
11788                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11789                 --
11790                 debit_balance := deb.amount;
11791                 --
11792                 -- Loop over the funding source credits that are eligible
11793                 -- to pay for this debit
11794                 --
11795                 FOR fund_credit IN
11796                         SELECT
11797                                 credit
11798                         FROM
11799                                 t_fund_credit
11800                         WHERE
11801                                 fund = deb.fund
11802                         ORDER BY
11803                                 seq
11804                 LOOP
11805                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11806                         --
11807                         -- Look up the balance for this credit.  If it's zero, then
11808                         -- it's not useful, so treat it as if you didn't find it.
11809                         -- (Actually there shouldn't be any zero balances in the table,
11810                         -- but we check just to make sure.)
11811                         --
11812                         SELECT *
11813                         INTO curr_credit_bal
11814                         FROM t_credit
11815                         WHERE
11816                                 credit = fund_credit.credit
11817                                 AND balance > 0;
11818                         --
11819                         IF curr_credit_bal IS NULL THEN
11820                                 --
11821                                 -- This credit is exhausted; try the next one.
11822                                 --
11823                                 CONTINUE;
11824                         END IF;
11825                         --
11826                         --
11827                         -- At this point we have an applicable credit with some money left.
11828                         -- Now see if the relevant funding_source has any money left.
11829                         --
11830                         -- Look up the balance of the allocation for this combination of
11831                         -- fund and source.  If you find such an entry, but it has a zero
11832                         -- balance, then it's not useful, so treat it as unfound.
11833                         -- (Actually there shouldn't be any zero balances in the table,
11834                         -- but we check just to make sure.)
11835                         --
11836                         SELECT *
11837                         INTO curr_fund_source_bal
11838                         FROM t_fund_source_bal
11839                         WHERE
11840                                 fund = deb.fund
11841                                 AND source = curr_credit_bal.source
11842                                 AND balance > 0;
11843                         --
11844                         IF curr_fund_source_bal IS NULL THEN
11845                                 --
11846                                 -- This fund/source doesn't exist or is already exhausted,
11847                                 -- so we can't use this credit.  Go on to the next one.
11848                                 --
11849                                 CONTINUE;
11850                         END IF;
11851                         --
11852                         -- Convert the available balances to the currency of the fund
11853                         --
11854                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11855                                 curr_credit_bal.currency_type, deb.currency_type );
11856                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11857                                 curr_credit_bal.currency_type, deb.currency_type );
11858                         --
11859                         -- Determine how much we can attribute to this credit: the minimum
11860                         -- of the debit amount, the fund/source balance, and the
11861                         -- credit balance
11862                         --
11863                         --RAISE NOTICE '   deb bal %', debit_balance;
11864                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11865                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11866                         --
11867                         conv_attr_amount := NULL;
11868                         attr_amount := debit_balance;
11869                         --
11870                         IF attr_amount > conv_alloc_balance THEN
11871                                 attr_amount := conv_alloc_balance;
11872                                 conv_attr_amount := curr_fund_source_bal.balance;
11873                         END IF;
11874                         IF attr_amount > conv_cred_balance THEN
11875                                 attr_amount := conv_cred_balance;
11876                                 conv_attr_amount := curr_credit_bal.balance;
11877                         END IF;
11878                         --
11879                         -- If we're attributing all of one of the balances, then that's how
11880                         -- much we will deduct from the balances, and we already captured
11881                         -- that amount above.  Otherwise we must convert the amount of the
11882                         -- attribution from the currency of the fund back to the currency of
11883                         -- the funding source.
11884                         --
11885                         IF conv_attr_amount IS NULL THEN
11886                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11887                                         deb.currency_type, curr_credit_bal.currency_type );
11888                         END IF;
11889                         --
11890                         -- Insert a row to record the attribution
11891                         --
11892                         attrib_count := attrib_count + 1;
11893                         INSERT INTO acq.debit_attribution (
11894                                 id,
11895                                 fund_debit,
11896                                 debit_amount,
11897                                 funding_source_credit,
11898                                 credit_amount
11899                         ) VALUES (
11900                                 attrib_count,
11901                                 deb.id,
11902                                 attr_amount,
11903                                 curr_credit_bal.credit,
11904                                 conv_attr_amount
11905                         );
11906                         --
11907                         -- Subtract the attributed amount from the various balances
11908                         --
11909                         debit_balance := debit_balance - attr_amount;
11910                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11911                         --
11912                         IF curr_fund_source_bal.balance <= 0 THEN
11913                                 --
11914                                 -- This allocation is exhausted.  Delete it so
11915                                 -- that we don't waste time looking at it again.
11916                                 --
11917                                 DELETE FROM t_fund_source_bal
11918                                 WHERE
11919                                         fund = curr_fund_source_bal.fund
11920                                         AND source = curr_fund_source_bal.source;
11921                         ELSE
11922                                 UPDATE t_fund_source_bal
11923                                 SET balance = balance - conv_attr_amount
11924                                 WHERE
11925                                         fund = curr_fund_source_bal.fund
11926                                         AND source = curr_fund_source_bal.source;
11927                         END IF;
11928                         --
11929                         IF curr_credit_bal.balance <= 0 THEN
11930                                 --
11931                                 -- This funding source credit is exhausted.  Delete it
11932                                 -- so that we don't waste time looking at it again.
11933                                 --
11934                                 --DELETE FROM t_credit
11935                                 --WHERE
11936                                 --      credit = curr_credit_bal.credit;
11937                                 --
11938                                 DELETE FROM t_fund_credit
11939                                 WHERE
11940                                         credit = curr_credit_bal.credit;
11941                         ELSE
11942                                 UPDATE t_credit
11943                                 SET balance = curr_credit_bal.balance
11944                                 WHERE
11945                                         credit = curr_credit_bal.credit;
11946                         END IF;
11947                         --
11948                         -- Are we done with this debit yet?
11949                         --
11950                         IF debit_balance <= 0 THEN
11951                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
11952                         END IF;
11953                 END LOOP;       -- End loop over credits
11954                 --
11955                 IF debit_balance <> 0 THEN
11956                         --
11957                         -- We weren't able to attribute this debit, or at least not
11958                         -- all of it.  Insert a row for the unattributed balance.
11959                         --
11960                         attrib_count := attrib_count + 1;
11961                         INSERT INTO acq.debit_attribution (
11962                                 id,
11963                                 fund_debit,
11964                                 debit_amount,
11965                                 funding_source_credit,
11966                                 credit_amount
11967                         ) VALUES (
11968                                 attrib_count,
11969                                 deb.id,
11970                                 debit_balance,
11971                                 NULL,
11972                                 NULL
11973                         );
11974                 END IF;
11975         END LOOP;   -- End of loop over debits
11976 END;
11977 $$ LANGUAGE 'plpgsql';
11978
11979 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
11980 DECLARE
11981     query TEXT;
11982     output TEXT;
11983 BEGIN
11984     query := $q$
11985         SELECT  regexp_replace(
11986                     oils_xpath_string(
11987                         $q$ || quote_literal($3) || $q$,
11988                         marc,
11989                         ' '
11990                     ),
11991                     $q$ || quote_literal($4) || $q$,
11992                     '',
11993                     'g')
11994           FROM  $q$ || $1 || $q$
11995           WHERE id = $q$ || $2;
11996
11997     EXECUTE query INTO output;
11998
11999     -- RAISE NOTICE 'query: %, output; %', query, output;
12000
12001     RETURN output;
12002 END;
12003 $$ LANGUAGE PLPGSQL IMMUTABLE;
12004
12005 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
12006     SELECT extract_marc_field($1,$2,$3,'');
12007 $$ LANGUAGE SQL IMMUTABLE;
12008
12009 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
12010 DECLARE
12011     moved_objects INT := 0;
12012     source_cn     asset.call_number%ROWTYPE;
12013     target_cn     asset.call_number%ROWTYPE;
12014     metarec       metabib.metarecord%ROWTYPE;
12015     hold          action.hold_request%ROWTYPE;
12016     ser_rec       serial.record_entry%ROWTYPE;
12017     uri_count     INT := 0;
12018     counter       INT := 0;
12019     uri_datafield TEXT;
12020     uri_text      TEXT := '';
12021 BEGIN
12022
12023     -- move any 856 entries on records that have at least one MARC-mapped URI entry
12024     SELECT  INTO uri_count COUNT(*)
12025       FROM  asset.uri_call_number_map m
12026             JOIN asset.call_number cn ON (m.call_number = cn.id)
12027       WHERE cn.record = source_record;
12028
12029     IF uri_count > 0 THEN
12030
12031         SELECT  COUNT(*) INTO counter
12032           FROM  oils_xpath_table(
12033                     'id',
12034                     'marc',
12035                     'biblio.record_entry',
12036                     '//*[@tag="856"]',
12037                     'id=' || source_record
12038                 ) as t(i int,c text);
12039
12040         FOR i IN 1 .. counter LOOP
12041             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
12042                         ' tag="856"' || 
12043                         ' ind1="' || FIRST(ind1) || '"'  || 
12044                         ' ind2="' || FIRST(ind2) || '">' || 
12045                         array_to_string(
12046                             array_accum(
12047                                 '<subfield code="' || subfield || '">' ||
12048                                 regexp_replace(
12049                                     regexp_replace(
12050                                         regexp_replace(data,'&','&amp;','g'),
12051                                         '>', '&gt;', 'g'
12052                                     ),
12053                                     '<', '&lt;', 'g'
12054                                 ) || '</subfield>'
12055                             ), ''
12056                         ) || '</datafield>' INTO uri_datafield
12057               FROM  oils_xpath_table(
12058                         'id',
12059                         'marc',
12060                         'biblio.record_entry',
12061                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
12062                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
12063                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
12064                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
12065                         'id=' || source_record
12066                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
12067
12068             uri_text := uri_text || uri_datafield;
12069         END LOOP;
12070
12071         IF uri_text <> '' THEN
12072             UPDATE  biblio.record_entry
12073               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
12074               WHERE id = target_record;
12075         END IF;
12076
12077     END IF;
12078
12079     -- Find and move metarecords to the target record
12080     SELECT  INTO metarec *
12081       FROM  metabib.metarecord
12082       WHERE master_record = source_record;
12083
12084     IF FOUND THEN
12085         UPDATE  metabib.metarecord
12086           SET   master_record = target_record,
12087             mods = NULL
12088           WHERE id = metarec.id;
12089
12090         moved_objects := moved_objects + 1;
12091     END IF;
12092
12093     -- Find call numbers attached to the source ...
12094     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
12095
12096         SELECT  INTO target_cn *
12097           FROM  asset.call_number
12098           WHERE label = source_cn.label
12099             AND owning_lib = source_cn.owning_lib
12100             AND record = target_record;
12101
12102         -- ... and if there's a conflicting one on the target ...
12103         IF FOUND THEN
12104
12105             -- ... move the copies to that, and ...
12106             UPDATE  asset.copy
12107               SET   call_number = target_cn.id
12108               WHERE call_number = source_cn.id;
12109
12110             -- ... move V holds to the move-target call number
12111             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
12112
12113                 UPDATE  action.hold_request
12114                   SET   target = target_cn.id
12115                   WHERE id = hold.id;
12116
12117                 moved_objects := moved_objects + 1;
12118             END LOOP;
12119
12120         -- ... if not ...
12121         ELSE
12122             -- ... just move the call number to the target record
12123             UPDATE  asset.call_number
12124               SET   record = target_record
12125               WHERE id = source_cn.id;
12126         END IF;
12127
12128         moved_objects := moved_objects + 1;
12129     END LOOP;
12130
12131     -- Find T holds targeting the source record ...
12132     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
12133
12134         -- ... and move them to the target record
12135         UPDATE  action.hold_request
12136           SET   target = target_record
12137           WHERE id = hold.id;
12138
12139         moved_objects := moved_objects + 1;
12140     END LOOP;
12141
12142     -- Find serial records targeting the source record ...
12143     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
12144         -- ... and move them to the target record
12145         UPDATE  serial.record_entry
12146           SET   record = target_record
12147           WHERE id = ser_rec.id;
12148
12149         moved_objects := moved_objects + 1;
12150     END LOOP;
12151
12152     -- Finally, "delete" the source record
12153     DELETE FROM biblio.record_entry WHERE id = source_record;
12154
12155     -- That's all, folks!
12156     RETURN moved_objects;
12157 END;
12158 $func$ LANGUAGE plpgsql;
12159
12160 CREATE OR REPLACE FUNCTION acq.transfer_fund(
12161         old_fund   IN INT,
12162         old_amount IN NUMERIC,     -- in currency of old fund
12163         new_fund   IN INT,
12164         new_amount IN NUMERIC,     -- in currency of new fund
12165         user_id    IN INT,
12166         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
12167         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
12168 ) RETURNS VOID AS $$
12169 /* -------------------------------------------------------------------------------
12170
12171 Function to transfer money from one fund to another.
12172
12173 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12174 negative amount for the old (losing) fund and a positive amount for the new
12175 (gaining) fund.  In some cases there may be more than one such pair of entries
12176 in order to pull the money from different funding sources, or more specifically
12177 from different funding source credits.  For each such pair there is also an
12178 entry in acq.fund_transfer.
12179
12180 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12181 choose a funding source for the transferred money to come from.  This choice
12182 must meet two constraints, so far as possible:
12183
12184 1. The amount transferred from a given funding source must not exceed the
12185 amount allocated to the old fund by the funding source.  To that end we
12186 compare the amount being transferred to the amount allocated.
12187
12188 2. We shouldn't transfer money that has already been spent or encumbered, as
12189 defined by the funding attribution process.  We attribute expenses to the
12190 oldest funding source credits first.  In order to avoid transferring that
12191 attributed money, we reverse the priority, transferring from the newest funding
12192 source credits first.  There can be no guarantee that this approach will
12193 avoid overcommitting a fund, but no other approach can do any better.
12194
12195 In this context the age of a funding source credit is defined by the
12196 deadline_date for credits with deadline_dates, and by the effective_date for
12197 credits without deadline_dates, with the proviso that credits with deadline_dates
12198 are all considered "older" than those without.
12199
12200 ----------
12201
12202 In the signature for this function, there is one last parameter commented out,
12203 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12204 driving the main loop has an OR clause commented out, which references the
12205 funding_source_in parameter.
12206
12207 If these lines are uncommented, this function will allow the user optionally to
12208 restrict a fund transfer to a specified funding source.  If the source
12209 parameter is left NULL, then there will be no such restriction.
12210
12211 ------------------------------------------------------------------------------- */ 
12212 DECLARE
12213         same_currency      BOOLEAN;
12214         currency_ratio     NUMERIC;
12215         old_fund_currency  TEXT;
12216         old_remaining      NUMERIC;  -- in currency of old fund
12217         new_fund_currency  TEXT;
12218         new_fund_active    BOOLEAN;
12219         new_remaining      NUMERIC;  -- in currency of new fund
12220         curr_old_amt       NUMERIC;  -- in currency of old fund
12221         curr_new_amt       NUMERIC;  -- in currency of new fund
12222         source_addition    NUMERIC;  -- in currency of funding source
12223         source_deduction   NUMERIC;  -- in currency of funding source
12224         orig_allocated_amt NUMERIC;  -- in currency of funding source
12225         allocated_amt      NUMERIC;  -- in currency of fund
12226         source             RECORD;
12227 BEGIN
12228         --
12229         -- Sanity checks
12230         --
12231         IF old_fund IS NULL THEN
12232                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12233         END IF;
12234         --
12235         IF old_amount IS NULL THEN
12236                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12237         END IF;
12238         --
12239         -- The new fund and its amount must be both NULL or both not NULL.
12240         --
12241         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12242                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12243         END IF;
12244         --
12245         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12246                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12247         END IF;
12248         --
12249         IF user_id IS NULL THEN
12250                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12251         END IF;
12252         --
12253         -- Initialize the amounts to be transferred, each denominated
12254         -- in the currency of its respective fund.  They will be
12255         -- reduced on each iteration of the loop.
12256         --
12257         old_remaining := old_amount;
12258         new_remaining := new_amount;
12259         --
12260         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12261         --      old_amount, old_fund, new_amount, new_fund;
12262         --
12263         -- Get the currency types of the old and new funds.
12264         --
12265         SELECT
12266                 currency_type
12267         INTO
12268                 old_fund_currency
12269         FROM
12270                 acq.fund
12271         WHERE
12272                 id = old_fund;
12273         --
12274         IF old_fund_currency IS NULL THEN
12275                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12276         END IF;
12277         --
12278         IF new_fund IS NOT NULL THEN
12279                 SELECT
12280                         currency_type,
12281                         active
12282                 INTO
12283                         new_fund_currency,
12284                         new_fund_active
12285                 FROM
12286                         acq.fund
12287                 WHERE
12288                         id = new_fund;
12289                 --
12290                 IF new_fund_currency IS NULL THEN
12291                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12292                 ELSIF NOT new_fund_active THEN
12293                         --
12294                         -- No point in putting money into a fund from whence you can't spend it
12295                         --
12296                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12297                 END IF;
12298                 --
12299                 IF new_amount = old_amount THEN
12300                         same_currency := true;
12301                         currency_ratio := 1;
12302                 ELSE
12303                         --
12304                         -- We'll have to translate currency between funds.  We presume that
12305                         -- the calling code has already applied an appropriate exchange rate,
12306                         -- so we'll apply the same conversion to each sub-transfer.
12307                         --
12308                         same_currency := false;
12309                         currency_ratio := new_amount / old_amount;
12310                 END IF;
12311         END IF;
12312         --
12313         -- Identify the funding source(s) from which we want to transfer the money.
12314         -- The principle is that we want to transfer the newest money first, because
12315         -- we spend the oldest money first.  The priority for spending is defined
12316         -- by a sort of the view acq.ordered_funding_source_credit.
12317         --
12318         FOR source in
12319                 SELECT
12320                         ofsc.id,
12321                         ofsc.funding_source,
12322                         ofsc.amount,
12323                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12324                                 AS converted_amt,
12325                         fs.currency_type
12326                 FROM
12327                         acq.ordered_funding_source_credit AS ofsc,
12328                         acq.funding_source fs
12329                 WHERE
12330                         ofsc.funding_source = fs.id
12331                         and ofsc.funding_source IN
12332                         (
12333                                 SELECT funding_source
12334                                 FROM acq.fund_allocation
12335                                 WHERE fund = old_fund
12336                         )
12337                         -- and
12338                         -- (
12339                         --      ofsc.funding_source = funding_source_in
12340                         --      OR funding_source_in IS NULL
12341                         -- )
12342                 ORDER BY
12343                         ofsc.sort_priority desc,
12344                         ofsc.sort_date desc,
12345                         ofsc.id desc
12346         LOOP
12347                 --
12348                 -- Determine how much money the old fund got from this funding source,
12349                 -- denominated in the currency types of the source and of the fund.
12350                 -- This result may reflect transfers from previous iterations.
12351                 --
12352                 SELECT
12353                         COALESCE( sum( amount ), 0 ),
12354                         COALESCE( sum( amount )
12355                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12356                 INTO
12357                         orig_allocated_amt,     -- in currency of the source
12358                         allocated_amt           -- in currency of the old fund
12359                 FROM
12360                         acq.fund_allocation
12361                 WHERE
12362                         fund = old_fund
12363                         and funding_source = source.funding_source;
12364                 --      
12365                 -- Determine how much to transfer from this credit, in the currency
12366                 -- of the fund.   Begin with the amount remaining to be attributed:
12367                 --
12368                 curr_old_amt := old_remaining;
12369                 --
12370                 -- Can't attribute more than was allocated from the fund:
12371                 --
12372                 IF curr_old_amt > allocated_amt THEN
12373                         curr_old_amt := allocated_amt;
12374                 END IF;
12375                 --
12376                 -- Can't attribute more than the amount of the current credit:
12377                 --
12378                 IF curr_old_amt > source.converted_amt THEN
12379                         curr_old_amt := source.converted_amt;
12380                 END IF;
12381                 --
12382                 curr_old_amt := trunc( curr_old_amt, 2 );
12383                 --
12384                 old_remaining := old_remaining - curr_old_amt;
12385                 --
12386                 -- Determine the amount to be deducted, if any,
12387                 -- from the old allocation.
12388                 --
12389                 IF old_remaining > 0 THEN
12390                         --
12391                         -- In this case we're using the whole allocation, so use that
12392                         -- amount directly instead of applying a currency translation
12393                         -- and thereby inviting round-off errors.
12394                         --
12395                         source_deduction := - orig_allocated_amt;
12396                 ELSE 
12397                         source_deduction := trunc(
12398                                 ( - curr_old_amt ) *
12399                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12400                                 2 );
12401                 END IF;
12402                 --
12403                 IF source_deduction <> 0 THEN
12404                         --
12405                         -- Insert negative allocation for old fund in fund_allocation,
12406                         -- converted into the currency of the funding source
12407                         --
12408                         INSERT INTO acq.fund_allocation (
12409                                 funding_source,
12410                                 fund,
12411                                 amount,
12412                                 allocator,
12413                                 note
12414                         ) VALUES (
12415                                 source.funding_source,
12416                                 old_fund,
12417                                 source_deduction,
12418                                 user_id,
12419                                 'Transfer to fund ' || new_fund
12420                         );
12421                 END IF;
12422                 --
12423                 IF new_fund IS NOT NULL THEN
12424                         --
12425                         -- Determine how much to add to the new fund, in
12426                         -- its currency, and how much remains to be added:
12427                         --
12428                         IF same_currency THEN
12429                                 curr_new_amt := curr_old_amt;
12430                         ELSE
12431                                 IF old_remaining = 0 THEN
12432                                         --
12433                                         -- This is the last iteration, so nothing should be left
12434                                         --
12435                                         curr_new_amt := new_remaining;
12436                                         new_remaining := 0;
12437                                 ELSE
12438                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12439                                         new_remaining := new_remaining - curr_new_amt;
12440                                 END IF;
12441                         END IF;
12442                         --
12443                         -- Determine how much to add, if any,
12444                         -- to the new fund's allocation.
12445                         --
12446                         IF old_remaining > 0 THEN
12447                                 --
12448                                 -- In this case we're using the whole allocation, so use that amount
12449                                 -- amount directly instead of applying a currency translation and
12450                                 -- thereby inviting round-off errors.
12451                                 --
12452                                 source_addition := orig_allocated_amt;
12453                         ELSIF source.currency_type = old_fund_currency THEN
12454                                 --
12455                                 -- In this case we don't need a round trip currency translation,
12456                                 -- thereby inviting round-off errors:
12457                                 --
12458                                 source_addition := curr_old_amt;
12459                         ELSE 
12460                                 source_addition := trunc(
12461                                         curr_new_amt *
12462                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12463                                         2 );
12464                         END IF;
12465                         --
12466                         IF source_addition <> 0 THEN
12467                                 --
12468                                 -- Insert positive allocation for new fund in fund_allocation,
12469                                 -- converted to the currency of the founding source
12470                                 --
12471                                 INSERT INTO acq.fund_allocation (
12472                                         funding_source,
12473                                         fund,
12474                                         amount,
12475                                         allocator,
12476                                         note
12477                                 ) VALUES (
12478                                         source.funding_source,
12479                                         new_fund,
12480                                         source_addition,
12481                                         user_id,
12482                                         'Transfer from fund ' || old_fund
12483                                 );
12484                         END IF;
12485                 END IF;
12486                 --
12487                 IF trunc( curr_old_amt, 2 ) <> 0
12488                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12489                         --
12490                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12491                         --
12492                         INSERT INTO acq.fund_transfer (
12493                                 src_fund,
12494                                 src_amount,
12495                                 dest_fund,
12496                                 dest_amount,
12497                                 transfer_user,
12498                                 note,
12499                                 funding_source_credit
12500                         ) VALUES (
12501                                 old_fund,
12502                                 trunc( curr_old_amt, 2 ),
12503                                 new_fund,
12504                                 trunc( curr_new_amt, 2 ),
12505                                 user_id,
12506                                 xfer_note,
12507                                 source.id
12508                         );
12509                 END IF;
12510                 --
12511                 if old_remaining <= 0 THEN
12512                         EXIT;                   -- Nothing more to be transferred
12513                 END IF;
12514         END LOOP;
12515 END;
12516 $$ LANGUAGE plpgsql;
12517
12518 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
12519         old_year INTEGER,
12520         user_id INTEGER,
12521         org_unit_id INTEGER
12522 ) RETURNS VOID AS $$
12523 DECLARE
12524 --
12525 new_id      INT;
12526 old_fund    RECORD;
12527 org_found   BOOLEAN;
12528 --
12529 BEGIN
12530         --
12531         -- Sanity checks
12532         --
12533         IF old_year IS NULL THEN
12534                 RAISE EXCEPTION 'Input year argument is NULL';
12535         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12536                 RAISE EXCEPTION 'Input year is out of range';
12537         END IF;
12538         --
12539         IF user_id IS NULL THEN
12540                 RAISE EXCEPTION 'Input user id argument is NULL';
12541         END IF;
12542         --
12543         IF org_unit_id IS NULL THEN
12544                 RAISE EXCEPTION 'Org unit id argument is NULL';
12545         ELSE
12546                 SELECT TRUE INTO org_found
12547                 FROM actor.org_unit
12548                 WHERE id = org_unit_id;
12549                 --
12550                 IF org_found IS NULL THEN
12551                         RAISE EXCEPTION 'Org unit id is invalid';
12552                 END IF;
12553         END IF;
12554         --
12555         -- Loop over the applicable funds
12556         --
12557         FOR old_fund in SELECT * FROM acq.fund
12558         WHERE
12559                 year = old_year
12560                 AND propagate
12561                 AND org = org_unit_id
12562         LOOP
12563                 BEGIN
12564                         INSERT INTO acq.fund (
12565                                 org,
12566                                 name,
12567                                 year,
12568                                 currency_type,
12569                                 code,
12570                                 rollover,
12571                                 propagate,
12572                                 balance_warning_percent,
12573                                 balance_stop_percent
12574                         ) VALUES (
12575                                 old_fund.org,
12576                                 old_fund.name,
12577                                 old_year + 1,
12578                                 old_fund.currency_type,
12579                                 old_fund.code,
12580                                 old_fund.rollover,
12581                                 true,
12582                                 old_fund.balance_warning_percent,
12583                                 old_fund.balance_stop_percent
12584                         )
12585                         RETURNING id INTO new_id;
12586                 EXCEPTION
12587                         WHEN unique_violation THEN
12588                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12589                                 CONTINUE;
12590                 END;
12591                 --RAISE NOTICE 'Propagating fund % to fund %',
12592                 --      old_fund.code, new_id;
12593         END LOOP;
12594 END;
12595 $$ LANGUAGE plpgsql;
12596
12597 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12598         old_year INTEGER,
12599         user_id INTEGER,
12600         org_unit_id INTEGER
12601 ) RETURNS VOID AS $$
12602 DECLARE
12603 --
12604 new_id      INT;
12605 old_fund    RECORD;
12606 org_found   BOOLEAN;
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                 SELECT TRUE INTO org_found
12626                 FROM actor.org_unit
12627                 WHERE id = org_unit_id;
12628                 --
12629                 IF org_found IS NULL THEN
12630                         RAISE EXCEPTION 'Org unit id is invalid';
12631                 END IF;
12632         END IF;
12633         --
12634         -- Loop over the applicable funds
12635         --
12636         FOR old_fund in SELECT * FROM acq.fund
12637         WHERE
12638                 year = old_year
12639                 AND propagate
12640                 AND org in (
12641                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12642                 )
12643         LOOP
12644                 BEGIN
12645                         INSERT INTO acq.fund (
12646                                 org,
12647                                 name,
12648                                 year,
12649                                 currency_type,
12650                                 code,
12651                                 rollover,
12652                                 propagate,
12653                                 balance_warning_percent,
12654                                 balance_stop_percent
12655                         ) VALUES (
12656                                 old_fund.org,
12657                                 old_fund.name,
12658                                 old_year + 1,
12659                                 old_fund.currency_type,
12660                                 old_fund.code,
12661                                 old_fund.rollover,
12662                                 true,
12663                                 old_fund.balance_warning_percent,
12664                                 old_fund.balance_stop_percent
12665                         )
12666                         RETURNING id INTO new_id;
12667                 EXCEPTION
12668                         WHEN unique_violation THEN
12669                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12670                                 CONTINUE;
12671                 END;
12672                 --RAISE NOTICE 'Propagating fund % to fund %',
12673                 --      old_fund.code, new_id;
12674         END LOOP;
12675 END;
12676 $$ LANGUAGE plpgsql;
12677
12678 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12679         old_year INTEGER,
12680         user_id INTEGER,
12681         org_unit_id INTEGER
12682 ) RETURNS VOID AS $$
12683 DECLARE
12684 --
12685 new_fund    INT;
12686 new_year    INT := old_year + 1;
12687 org_found   BOOL;
12688 xfer_amount NUMERIC;
12689 roll_fund   RECORD;
12690 deb         RECORD;
12691 detail      RECORD;
12692 --
12693 BEGIN
12694         --
12695         -- Sanity checks
12696         --
12697         IF old_year IS NULL THEN
12698                 RAISE EXCEPTION 'Input year argument is NULL';
12699     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12700         RAISE EXCEPTION 'Input year is out of range';
12701         END IF;
12702         --
12703         IF user_id IS NULL THEN
12704                 RAISE EXCEPTION 'Input user id argument is NULL';
12705         END IF;
12706         --
12707         IF org_unit_id IS NULL THEN
12708                 RAISE EXCEPTION 'Org unit id argument is NULL';
12709         ELSE
12710                 --
12711                 -- Validate the org unit
12712                 --
12713                 SELECT TRUE
12714                 INTO org_found
12715                 FROM actor.org_unit
12716                 WHERE id = org_unit_id;
12717                 --
12718                 IF org_found IS NULL THEN
12719                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12720                 END IF;
12721         END IF;
12722         --
12723         -- Loop over the propagable funds to identify the details
12724         -- from the old fund plus the id of the new one, if it exists.
12725         --
12726         FOR roll_fund in
12727         SELECT
12728             oldf.id AS old_fund,
12729             oldf.org,
12730             oldf.name,
12731             oldf.currency_type,
12732             oldf.code,
12733                 oldf.rollover,
12734             newf.id AS new_fund_id
12735         FROM
12736         acq.fund AS oldf
12737         LEFT JOIN acq.fund AS newf
12738                 ON ( oldf.code = newf.code )
12739         WHERE
12740                     oldf.org = org_unit_id
12741                 and oldf.year = old_year
12742                 and oldf.propagate
12743         and newf.year = new_year
12744         LOOP
12745                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12746                 --
12747                 IF roll_fund.new_fund_id IS NULL THEN
12748                         --
12749                         -- The old fund hasn't been propagated yet.  Propagate it now.
12750                         --
12751                         INSERT INTO acq.fund (
12752                                 org,
12753                                 name,
12754                                 year,
12755                                 currency_type,
12756                                 code,
12757                                 rollover,
12758                                 propagate,
12759                                 balance_warning_percent,
12760                                 balance_stop_percent
12761                         ) VALUES (
12762                                 roll_fund.org,
12763                                 roll_fund.name,
12764                                 new_year,
12765                                 roll_fund.currency_type,
12766                                 roll_fund.code,
12767                                 true,
12768                                 true,
12769                                 roll_fund.balance_warning_percent,
12770                                 roll_fund.balance_stop_percent
12771                         )
12772                         RETURNING id INTO new_fund;
12773                 ELSE
12774                         new_fund = roll_fund.new_fund_id;
12775                 END IF;
12776                 --
12777                 -- Determine the amount to transfer
12778                 --
12779                 SELECT amount
12780                 INTO xfer_amount
12781                 FROM acq.fund_spent_balance
12782                 WHERE fund = roll_fund.old_fund;
12783                 --
12784                 IF xfer_amount <> 0 THEN
12785                         IF roll_fund.rollover THEN
12786                                 --
12787                                 -- Transfer balance from old fund to new
12788                                 --
12789                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12790                                 --
12791                                 PERFORM acq.transfer_fund(
12792                                         roll_fund.old_fund,
12793                                         xfer_amount,
12794                                         new_fund,
12795                                         xfer_amount,
12796                                         user_id,
12797                                         'Rollover'
12798                                 );
12799                         ELSE
12800                                 --
12801                                 -- Transfer balance from old fund to the void
12802                                 --
12803                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12804                                 --
12805                                 PERFORM acq.transfer_fund(
12806                                         roll_fund.old_fund,
12807                                         xfer_amount,
12808                                         NULL,
12809                                         NULL,
12810                                         user_id,
12811                                         'Rollover'
12812                                 );
12813                         END IF;
12814                 END IF;
12815                 --
12816                 IF roll_fund.rollover THEN
12817                         --
12818                         -- Move any lineitems from the old fund to the new one
12819                         -- where the associated debit is an encumbrance.
12820                         --
12821                         -- Any other tables tying expenditure details to funds should
12822                         -- receive similar treatment.  At this writing there are none.
12823                         --
12824                         UPDATE acq.lineitem_detail
12825                         SET fund = new_fund
12826                         WHERE
12827                         fund = roll_fund.old_fund -- this condition may be redundant
12828                         AND fund_debit in
12829                         (
12830                                 SELECT id
12831                                 FROM acq.fund_debit
12832                                 WHERE
12833                                 fund = roll_fund.old_fund
12834                                 AND encumbrance
12835                         );
12836                         --
12837                         -- Move encumbrance debits from the old fund to the new fund
12838                         --
12839                         UPDATE acq.fund_debit
12840                         SET fund = new_fund
12841                         wHERE
12842                                 fund = roll_fund.old_fund
12843                                 AND encumbrance;
12844                 END IF;
12845                 --
12846                 -- Mark old fund as inactive, now that we've closed it
12847                 --
12848                 UPDATE acq.fund
12849                 SET active = FALSE
12850                 WHERE id = roll_fund.old_fund;
12851         END LOOP;
12852 END;
12853 $$ LANGUAGE plpgsql;
12854
12855 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12856         old_year INTEGER,
12857         user_id INTEGER,
12858         org_unit_id INTEGER
12859 ) RETURNS VOID AS $$
12860 DECLARE
12861 --
12862 new_fund    INT;
12863 new_year    INT := old_year + 1;
12864 org_found   BOOL;
12865 xfer_amount NUMERIC;
12866 roll_fund   RECORD;
12867 deb         RECORD;
12868 detail      RECORD;
12869 --
12870 BEGIN
12871         --
12872         -- Sanity checks
12873         --
12874         IF old_year IS NULL THEN
12875                 RAISE EXCEPTION 'Input year argument is NULL';
12876     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12877         RAISE EXCEPTION 'Input year is out of range';
12878         END IF;
12879         --
12880         IF user_id IS NULL THEN
12881                 RAISE EXCEPTION 'Input user id argument is NULL';
12882         END IF;
12883         --
12884         IF org_unit_id IS NULL THEN
12885                 RAISE EXCEPTION 'Org unit id argument is NULL';
12886         ELSE
12887                 --
12888                 -- Validate the org unit
12889                 --
12890                 SELECT TRUE
12891                 INTO org_found
12892                 FROM actor.org_unit
12893                 WHERE id = org_unit_id;
12894                 --
12895                 IF org_found IS NULL THEN
12896                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12897                 END IF;
12898         END IF;
12899         --
12900         -- Loop over the propagable funds to identify the details
12901         -- from the old fund plus the id of the new one, if it exists.
12902         --
12903         FOR roll_fund in
12904         SELECT
12905             oldf.id AS old_fund,
12906             oldf.org,
12907             oldf.name,
12908             oldf.currency_type,
12909             oldf.code,
12910                 oldf.rollover,
12911             newf.id AS new_fund_id
12912         FROM
12913         acq.fund AS oldf
12914         LEFT JOIN acq.fund AS newf
12915                 ON ( oldf.code = newf.code )
12916         WHERE
12917                     oldf.year = old_year
12918                 AND oldf.propagate
12919         AND newf.year = new_year
12920                 AND oldf.org in (
12921                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12922                 )
12923         LOOP
12924                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12925                 --
12926                 IF roll_fund.new_fund_id IS NULL THEN
12927                         --
12928                         -- The old fund hasn't been propagated yet.  Propagate it now.
12929                         --
12930                         INSERT INTO acq.fund (
12931                                 org,
12932                                 name,
12933                                 year,
12934                                 currency_type,
12935                                 code,
12936                                 rollover,
12937                                 propagate,
12938                                 balance_warning_percent,
12939                                 balance_stop_percent
12940                         ) VALUES (
12941                                 roll_fund.org,
12942                                 roll_fund.name,
12943                                 new_year,
12944                                 roll_fund.currency_type,
12945                                 roll_fund.code,
12946                                 true,
12947                                 true,
12948                                 roll_fund.balance_warning_percent,
12949                                 roll_fund.balance_stop_percent
12950                         )
12951                         RETURNING id INTO new_fund;
12952                 ELSE
12953                         new_fund = roll_fund.new_fund_id;
12954                 END IF;
12955                 --
12956                 -- Determine the amount to transfer
12957                 --
12958                 SELECT amount
12959                 INTO xfer_amount
12960                 FROM acq.fund_spent_balance
12961                 WHERE fund = roll_fund.old_fund;
12962                 --
12963                 IF xfer_amount <> 0 THEN
12964                         IF roll_fund.rollover THEN
12965                                 --
12966                                 -- Transfer balance from old fund to new
12967                                 --
12968                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12969                                 --
12970                                 PERFORM acq.transfer_fund(
12971                                         roll_fund.old_fund,
12972                                         xfer_amount,
12973                                         new_fund,
12974                                         xfer_amount,
12975                                         user_id,
12976                                         'Rollover'
12977                                 );
12978                         ELSE
12979                                 --
12980                                 -- Transfer balance from old fund to the void
12981                                 --
12982                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12983                                 --
12984                                 PERFORM acq.transfer_fund(
12985                                         roll_fund.old_fund,
12986                                         xfer_amount,
12987                                         NULL,
12988                                         NULL,
12989                                         user_id,
12990                                         'Rollover'
12991                                 );
12992                         END IF;
12993                 END IF;
12994                 --
12995                 IF roll_fund.rollover THEN
12996                         --
12997                         -- Move any lineitems from the old fund to the new one
12998                         -- where the associated debit is an encumbrance.
12999                         --
13000                         -- Any other tables tying expenditure details to funds should
13001                         -- receive similar treatment.  At this writing there are none.
13002                         --
13003                         UPDATE acq.lineitem_detail
13004                         SET fund = new_fund
13005                         WHERE
13006                         fund = roll_fund.old_fund -- this condition may be redundant
13007                         AND fund_debit in
13008                         (
13009                                 SELECT id
13010                                 FROM acq.fund_debit
13011                                 WHERE
13012                                 fund = roll_fund.old_fund
13013                                 AND encumbrance
13014                         );
13015                         --
13016                         -- Move encumbrance debits from the old fund to the new fund
13017                         --
13018                         UPDATE acq.fund_debit
13019                         SET fund = new_fund
13020                         wHERE
13021                                 fund = roll_fund.old_fund
13022                                 AND encumbrance;
13023                 END IF;
13024                 --
13025                 -- Mark old fund as inactive, now that we've closed it
13026                 --
13027                 UPDATE acq.fund
13028                 SET active = FALSE
13029                 WHERE id = roll_fund.old_fund;
13030         END LOOP;
13031 END;
13032 $$ LANGUAGE plpgsql;
13033
13034 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
13035     SELECT regexp_replace($1, ',', '', 'g');
13036 $$ LANGUAGE SQL STRICT IMMUTABLE;
13037
13038 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
13039     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
13040 $$ LANGUAGE SQL STRICT IMMUTABLE;
13041
13042 CREATE TABLE acq.distribution_formula_application (
13043     id BIGSERIAL PRIMARY KEY,
13044     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
13045     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
13046     formula INT NOT NULL
13047         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
13048     lineitem INT NOT NULL
13049         REFERENCES acq.lineitem( id )
13050                 ON DELETE CASCADE
13051                 DEFERRABLE INITIALLY DEFERRED
13052 );
13053
13054 CREATE INDEX acqdfa_df_idx
13055     ON acq.distribution_formula_application(formula);
13056 CREATE INDEX acqdfa_li_idx
13057     ON acq.distribution_formula_application(lineitem);
13058 CREATE INDEX acqdfa_creator_idx
13059     ON acq.distribution_formula_application(creator);
13060
13061 CREATE TABLE acq.user_request_type (
13062     id      SERIAL  PRIMARY KEY,
13063     label   TEXT    NOT NULL UNIQUE -- i18n-ize
13064 );
13065
13066 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
13067 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
13068 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
13069 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
13070 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
13071
13072 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
13073
13074 CREATE TABLE acq.cancel_reason (
13075         id            SERIAL            PRIMARY KEY,
13076         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
13077                                         DEFERRABLE INITIALLY DEFERRED,
13078         label         TEXT              NOT NULL,
13079         description   TEXT              NOT NULL,
13080         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
13081         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
13082 );
13083
13084 -- Reserve ids 1-999 for stock reasons
13085 -- Reserve ids 1000-1999 for EDI reasons
13086 -- 2000+ are available for staff to create
13087
13088 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
13089
13090 CREATE TABLE acq.user_request (
13091     id                  SERIAL  PRIMARY KEY,
13092     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
13093     hold                BOOL    NOT NULL DEFAULT TRUE,
13094
13095     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
13096     holdable_formats    TEXT,           -- nullable, for use in hold creation
13097     phone_notify        TEXT,
13098     email_notify        BOOL    NOT NULL DEFAULT TRUE,
13099     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
13100     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
13101     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
13102     need_before         TIMESTAMPTZ,    -- don't create holds after this
13103     max_fee             TEXT,
13104
13105     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
13106     isxn                TEXT,
13107     title               TEXT,
13108     volume              TEXT,
13109     author              TEXT,
13110     article_title       TEXT,
13111     article_pages       TEXT,
13112     publisher           TEXT,
13113     location            TEXT,
13114     pubdate             TEXT,
13115     mentioned           TEXT,
13116     other_info          TEXT,
13117         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
13118                                              DEFERRABLE INITIALLY DEFERRED
13119 );
13120
13121 CREATE TABLE acq.lineitem_alert_text (
13122         id               SERIAL         PRIMARY KEY,
13123         code             TEXT           NOT NULL,
13124         description      TEXT,
13125         owning_lib       INT            NOT NULL
13126                                         REFERENCES actor.org_unit(id)
13127                                         DEFERRABLE INITIALLY DEFERRED,
13128         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
13129 );
13130
13131 ALTER TABLE acq.lineitem_note
13132         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
13133                                          DEFERRABLE INITIALLY DEFERRED;
13134
13135 -- add ON DELETE CASCADE clause
13136
13137 ALTER TABLE acq.lineitem_note
13138         DROP CONSTRAINT lineitem_note_lineitem_fkey;
13139
13140 ALTER TABLE acq.lineitem_note
13141         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13142                 ON DELETE CASCADE
13143                 DEFERRABLE INITIALLY DEFERRED;
13144
13145 ALTER TABLE acq.lineitem_note
13146         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13147
13148 CREATE TABLE acq.invoice_method (
13149     code    TEXT    PRIMARY KEY,
13150     name    TEXT    NOT NULL -- i18n-ize
13151 );
13152 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
13153 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
13154
13155 CREATE TABLE acq.invoice_payment_method (
13156         code      TEXT     PRIMARY KEY,
13157         name      TEXT     NOT NULL
13158 );
13159
13160 CREATE TABLE acq.invoice (
13161     id             SERIAL      PRIMARY KEY,
13162     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
13163     provider       INT         NOT NULL REFERENCES acq.provider (id),
13164     shipper        INT         NOT NULL REFERENCES acq.provider (id),
13165     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13166     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
13167     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
13168     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
13169         payment_auth   TEXT,
13170         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
13171                                    DEFERRABLE INITIALLY DEFERRED,
13172         note           TEXT,
13173     complete       BOOL        NOT NULL DEFAULT FALSE,
13174     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13175 );
13176
13177 CREATE TABLE acq.invoice_entry (
13178     id              SERIAL      PRIMARY KEY,
13179     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13180     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13181     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13182     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13183     phys_item_count INT, -- and how many did staff count
13184     note            TEXT,
13185     billed_per_item BOOL,
13186     cost_billed     NUMERIC(8,2),
13187     actual_cost     NUMERIC(8,2),
13188         amount_paid     NUMERIC (8,2)
13189 );
13190
13191 CREATE TABLE acq.invoice_item_type (
13192     code    TEXT    PRIMARY KEY,
13193     name    TEXT    NOT NULL, -- i18n-ize
13194         prorate BOOL    NOT NULL DEFAULT FALSE
13195 );
13196
13197 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13198 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13199 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13200 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13201 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13202 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13203
13204 CREATE TABLE acq.po_item (
13205         id              SERIAL      PRIMARY KEY,
13206         purchase_order  INT         REFERENCES acq.purchase_order (id)
13207                                     ON UPDATE CASCADE ON DELETE SET NULL
13208                                     DEFERRABLE INITIALLY DEFERRED,
13209         fund_debit      INT         REFERENCES acq.fund_debit (id)
13210                                     DEFERRABLE INITIALLY DEFERRED,
13211         inv_item_type   TEXT        NOT NULL
13212                                     REFERENCES acq.invoice_item_type (code)
13213                                     DEFERRABLE INITIALLY DEFERRED,
13214         title           TEXT,
13215         author          TEXT,
13216         note            TEXT,
13217         estimated_cost  NUMERIC(8,2),
13218         fund            INT         REFERENCES acq.fund (id)
13219                                     DEFERRABLE INITIALLY DEFERRED,
13220         target          BIGINT
13221 );
13222
13223 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13224     id              SERIAL      PRIMARY KEY,
13225     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13226     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13227     fund_debit      INT         REFERENCES acq.fund_debit (id),
13228     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13229     title           TEXT,
13230     author          TEXT,
13231     note            TEXT,
13232     cost_billed     NUMERIC(8,2),
13233     actual_cost     NUMERIC(8,2),
13234     fund            INT         REFERENCES acq.fund (id)
13235                                 DEFERRABLE INITIALLY DEFERRED,
13236     amount_paid     NUMERIC (8,2),
13237     po_item         INT         REFERENCES acq.po_item (id)
13238                                 DEFERRABLE INITIALLY DEFERRED,
13239     target          BIGINT
13240 );
13241
13242 CREATE TABLE acq.edi_message (
13243     id               SERIAL          PRIMARY KEY,
13244     account          INTEGER         REFERENCES acq.edi_account(id)
13245                                      DEFERRABLE INITIALLY DEFERRED,
13246     remote_file      TEXT,
13247     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13248     translate_time   TIMESTAMPTZ,
13249     process_time     TIMESTAMPTZ,
13250     error_time       TIMESTAMPTZ,
13251     status           TEXT            NOT NULL DEFAULT 'new'
13252                                      CONSTRAINT status_value CHECK
13253                                      ( status IN (
13254                                         'new',          -- needs to be translated
13255                                         'translated',   -- needs to be processed
13256                                         'trans_error',  -- error in translation step
13257                                         'processed',    -- needs to have remote_file deleted
13258                                         'proc_error',   -- error in processing step
13259                                         'delete_error', -- error in deletion
13260                                         'retry',        -- need to retry
13261                                         'complete'      -- done
13262                                      )),
13263     edi              TEXT,
13264     jedi             TEXT,
13265     error            TEXT,
13266     purchase_order   INT             REFERENCES acq.purchase_order
13267                                      DEFERRABLE INITIALLY DEFERRED,
13268     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13269                                      CHECK ( message_type IN (
13270                                         'ORDERS',
13271                                         'ORDRSP',
13272                                         'INVOIC',
13273                                         'OSTENQ',
13274                                         'OSTRPT'
13275                                      ))
13276 );
13277
13278 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13279
13280 ALTER TABLE acq.provider_address
13281         ADD COLUMN fax_phone TEXT;
13282
13283 ALTER TABLE acq.provider_contact_address
13284         ADD COLUMN fax_phone TEXT;
13285
13286 CREATE TABLE acq.provider_note (
13287     id      SERIAL              PRIMARY KEY,
13288     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13289     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13290     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13291     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13292     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13293     value       TEXT            NOT NULL
13294 );
13295 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13296 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13297 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13298
13299 -- For each fund: the total allocation from all sources, in the
13300 -- currency of the fund (or 0 if there are no allocations)
13301
13302 CREATE VIEW acq.all_fund_allocation_total AS
13303 SELECT
13304     f.id AS fund,
13305     COALESCE( SUM( a.amount * acq.exchange_ratio(
13306         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13307     AS amount
13308 FROM
13309     acq.fund f
13310         LEFT JOIN acq.fund_allocation a
13311             ON a.fund = f.id
13312         LEFT JOIN acq.funding_source s
13313             ON a.funding_source = s.id
13314 GROUP BY
13315     f.id;
13316
13317 -- For every fund: the total encumbrances (or 0 if none),
13318 -- in the currency of the fund.
13319
13320 CREATE VIEW acq.all_fund_encumbrance_total AS
13321 SELECT
13322         f.id AS fund,
13323         COALESCE( encumb.amount, 0 ) AS amount
13324 FROM
13325         acq.fund AS f
13326                 LEFT JOIN (
13327                         SELECT
13328                                 fund,
13329                                 sum( amount ) AS amount
13330                         FROM
13331                                 acq.fund_debit
13332                         WHERE
13333                                 encumbrance
13334                         GROUP BY fund
13335                 ) AS encumb
13336                         ON f.id = encumb.fund;
13337
13338 -- For every fund: the total spent (or 0 if none),
13339 -- in the currency of the fund.
13340
13341 CREATE VIEW acq.all_fund_spent_total AS
13342 SELECT
13343     f.id AS fund,
13344     COALESCE( spent.amount, 0 ) AS amount
13345 FROM
13346     acq.fund AS f
13347         LEFT JOIN (
13348             SELECT
13349                 fund,
13350                 sum( amount ) AS amount
13351             FROM
13352                 acq.fund_debit
13353             WHERE
13354                 NOT encumbrance
13355             GROUP BY fund
13356         ) AS spent
13357             ON f.id = spent.fund;
13358
13359 -- For each fund: the amount not yet spent, in the currency
13360 -- of the fund.  May include encumbrances.
13361
13362 CREATE VIEW acq.all_fund_spent_balance AS
13363 SELECT
13364         c.fund,
13365         c.amount - d.amount AS amount
13366 FROM acq.all_fund_allocation_total c
13367     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13368
13369 -- For each fund: the amount neither spent nor encumbered,
13370 -- in the currency of the fund
13371
13372 CREATE VIEW acq.all_fund_combined_balance AS
13373 SELECT
13374      a.fund,
13375      a.amount - COALESCE( c.amount, 0 ) AS amount
13376 FROM
13377      acq.all_fund_allocation_total a
13378         LEFT OUTER JOIN (
13379             SELECT
13380                 fund,
13381                 SUM( amount ) AS amount
13382             FROM
13383                 acq.fund_debit
13384             GROUP BY
13385                 fund
13386         ) AS c USING ( fund );
13387
13388 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 $$
13389 DECLARE
13390         suffix TEXT;
13391         bucket_row RECORD;
13392         picklist_row RECORD;
13393         queue_row RECORD;
13394         folder_row RECORD;
13395 BEGIN
13396
13397     -- do some initial cleanup 
13398     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13399     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13400     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13401
13402     -- actor.*
13403     IF del_cards THEN
13404         DELETE FROM actor.card where usr = src_usr;
13405     ELSE
13406         IF deactivate_cards THEN
13407             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13408         END IF;
13409         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13410     END IF;
13411
13412
13413     IF del_addrs THEN
13414         DELETE FROM actor.usr_address WHERE usr = src_usr;
13415     ELSE
13416         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13417     END IF;
13418
13419     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13420     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13421     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13422     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13423     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13424
13425     -- permission.*
13426     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13427     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13428     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13429     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13430
13431
13432     -- container.*
13433         
13434         -- For each *_bucket table: transfer every bucket belonging to src_usr
13435         -- into the custody of dest_usr.
13436         --
13437         -- In order to avoid colliding with an existing bucket owned by
13438         -- the destination user, append the source user's id (in parenthesese)
13439         -- to the name.  If you still get a collision, add successive
13440         -- spaces to the name and keep trying until you succeed.
13441         --
13442         FOR bucket_row in
13443                 SELECT id, name
13444                 FROM   container.biblio_record_entry_bucket
13445                 WHERE  owner = src_usr
13446         LOOP
13447                 suffix := ' (' || src_usr || ')';
13448                 LOOP
13449                         BEGIN
13450                                 UPDATE  container.biblio_record_entry_bucket
13451                                 SET     owner = dest_usr, name = name || suffix
13452                                 WHERE   id = bucket_row.id;
13453                         EXCEPTION WHEN unique_violation THEN
13454                                 suffix := suffix || ' ';
13455                                 CONTINUE;
13456                         END;
13457                         EXIT;
13458                 END LOOP;
13459         END LOOP;
13460
13461         FOR bucket_row in
13462                 SELECT id, name
13463                 FROM   container.call_number_bucket
13464                 WHERE  owner = src_usr
13465         LOOP
13466                 suffix := ' (' || src_usr || ')';
13467                 LOOP
13468                         BEGIN
13469                                 UPDATE  container.call_number_bucket
13470                                 SET     owner = dest_usr, name = name || suffix
13471                                 WHERE   id = bucket_row.id;
13472                         EXCEPTION WHEN unique_violation THEN
13473                                 suffix := suffix || ' ';
13474                                 CONTINUE;
13475                         END;
13476                         EXIT;
13477                 END LOOP;
13478         END LOOP;
13479
13480         FOR bucket_row in
13481                 SELECT id, name
13482                 FROM   container.copy_bucket
13483                 WHERE  owner = src_usr
13484         LOOP
13485                 suffix := ' (' || src_usr || ')';
13486                 LOOP
13487                         BEGIN
13488                                 UPDATE  container.copy_bucket
13489                                 SET     owner = dest_usr, name = name || suffix
13490                                 WHERE   id = bucket_row.id;
13491                         EXCEPTION WHEN unique_violation THEN
13492                                 suffix := suffix || ' ';
13493                                 CONTINUE;
13494                         END;
13495                         EXIT;
13496                 END LOOP;
13497         END LOOP;
13498
13499         FOR bucket_row in
13500                 SELECT id, name
13501                 FROM   container.user_bucket
13502                 WHERE  owner = src_usr
13503         LOOP
13504                 suffix := ' (' || src_usr || ')';
13505                 LOOP
13506                         BEGIN
13507                                 UPDATE  container.user_bucket
13508                                 SET     owner = dest_usr, name = name || suffix
13509                                 WHERE   id = bucket_row.id;
13510                         EXCEPTION WHEN unique_violation THEN
13511                                 suffix := suffix || ' ';
13512                                 CONTINUE;
13513                         END;
13514                         EXIT;
13515                 END LOOP;
13516         END LOOP;
13517
13518         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13519
13520     -- vandelay.*
13521         -- transfer queues the same way we transfer buckets (see above)
13522         FOR queue_row in
13523                 SELECT id, name
13524                 FROM   vandelay.queue
13525                 WHERE  owner = src_usr
13526         LOOP
13527                 suffix := ' (' || src_usr || ')';
13528                 LOOP
13529                         BEGIN
13530                                 UPDATE  vandelay.queue
13531                                 SET     owner = dest_usr, name = name || suffix
13532                                 WHERE   id = queue_row.id;
13533                         EXCEPTION WHEN unique_violation THEN
13534                                 suffix := suffix || ' ';
13535                                 CONTINUE;
13536                         END;
13537                         EXIT;
13538                 END LOOP;
13539         END LOOP;
13540
13541     -- money.*
13542     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13543     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13544     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13545     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13546     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13547
13548     -- action.*
13549     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13550     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13551     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13552
13553     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13554     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13555     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13556     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13557
13558     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13559     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13560     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13561     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13562     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13563
13564     -- acq.*
13565     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13566         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13567
13568         -- transfer picklists the same way we transfer buckets (see above)
13569         FOR picklist_row in
13570                 SELECT id, name
13571                 FROM   acq.picklist
13572                 WHERE  owner = src_usr
13573         LOOP
13574                 suffix := ' (' || src_usr || ')';
13575                 LOOP
13576                         BEGIN
13577                                 UPDATE  acq.picklist
13578                                 SET     owner = dest_usr, name = name || suffix
13579                                 WHERE   id = picklist_row.id;
13580                         EXCEPTION WHEN unique_violation THEN
13581                                 suffix := suffix || ' ';
13582                                 CONTINUE;
13583                         END;
13584                         EXIT;
13585                 END LOOP;
13586         END LOOP;
13587
13588     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13589     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13590     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13591     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13592     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13593     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13594     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13595     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13596
13597     -- asset.*
13598     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13599     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13600     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13601     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13602     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13603     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13604
13605     -- serial.*
13606     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13607     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13608
13609     -- reporter.*
13610     -- It's not uncommon to define the reporter schema in a replica 
13611     -- DB only, so don't assume these tables exist in the write DB.
13612     BEGIN
13613         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13614     EXCEPTION WHEN undefined_table THEN
13615         -- do nothing
13616     END;
13617     BEGIN
13618         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13619     EXCEPTION WHEN undefined_table THEN
13620         -- do nothing
13621     END;
13622     BEGIN
13623         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13624     EXCEPTION WHEN undefined_table THEN
13625         -- do nothing
13626     END;
13627     BEGIN
13628                 -- transfer folders the same way we transfer buckets (see above)
13629                 FOR folder_row in
13630                         SELECT id, name
13631                         FROM   reporter.template_folder
13632                         WHERE  owner = src_usr
13633                 LOOP
13634                         suffix := ' (' || src_usr || ')';
13635                         LOOP
13636                                 BEGIN
13637                                         UPDATE  reporter.template_folder
13638                                         SET     owner = dest_usr, name = name || suffix
13639                                         WHERE   id = folder_row.id;
13640                                 EXCEPTION WHEN unique_violation THEN
13641                                         suffix := suffix || ' ';
13642                                         CONTINUE;
13643                                 END;
13644                                 EXIT;
13645                         END LOOP;
13646                 END LOOP;
13647     EXCEPTION WHEN undefined_table THEN
13648         -- do nothing
13649     END;
13650     BEGIN
13651                 -- transfer folders the same way we transfer buckets (see above)
13652                 FOR folder_row in
13653                         SELECT id, name
13654                         FROM   reporter.report_folder
13655                         WHERE  owner = src_usr
13656                 LOOP
13657                         suffix := ' (' || src_usr || ')';
13658                         LOOP
13659                                 BEGIN
13660                                         UPDATE  reporter.report_folder
13661                                         SET     owner = dest_usr, name = name || suffix
13662                                         WHERE   id = folder_row.id;
13663                                 EXCEPTION WHEN unique_violation THEN
13664                                         suffix := suffix || ' ';
13665                                         CONTINUE;
13666                                 END;
13667                                 EXIT;
13668                         END LOOP;
13669                 END LOOP;
13670     EXCEPTION WHEN undefined_table THEN
13671         -- do nothing
13672     END;
13673     BEGIN
13674                 -- transfer folders the same way we transfer buckets (see above)
13675                 FOR folder_row in
13676                         SELECT id, name
13677                         FROM   reporter.output_folder
13678                         WHERE  owner = src_usr
13679                 LOOP
13680                         suffix := ' (' || src_usr || ')';
13681                         LOOP
13682                                 BEGIN
13683                                         UPDATE  reporter.output_folder
13684                                         SET     owner = dest_usr, name = name || suffix
13685                                         WHERE   id = folder_row.id;
13686                                 EXCEPTION WHEN unique_violation THEN
13687                                         suffix := suffix || ' ';
13688                                         CONTINUE;
13689                                 END;
13690                                 EXIT;
13691                         END LOOP;
13692                 END LOOP;
13693     EXCEPTION WHEN undefined_table THEN
13694         -- do nothing
13695     END;
13696
13697     -- Finally, delete the source user
13698     DELETE FROM actor.usr WHERE id = src_usr;
13699
13700 END;
13701 $$ LANGUAGE plpgsql;
13702
13703 -- The "add" trigger functions should protect against existing NULLed values, just in case
13704 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13705 BEGIN
13706     IF NOT NEW.voided THEN
13707         UPDATE  money.materialized_billable_xact_summary
13708           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13709             last_billing_ts = NEW.billing_ts,
13710             last_billing_note = NEW.note,
13711             last_billing_type = NEW.billing_type,
13712             balance_owed = balance_owed + NEW.amount
13713           WHERE id = NEW.xact;
13714     END IF;
13715
13716     RETURN NEW;
13717 END;
13718 $$ LANGUAGE PLPGSQL;
13719
13720 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13721 BEGIN
13722     IF NOT NEW.voided THEN
13723         UPDATE  money.materialized_billable_xact_summary
13724           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13725             last_payment_ts = NEW.payment_ts,
13726             last_payment_note = NEW.note,
13727             last_payment_type = TG_ARGV[0],
13728             balance_owed = balance_owed - NEW.amount
13729           WHERE id = NEW.xact;
13730     END IF;
13731
13732     RETURN NEW;
13733 END;
13734 $$ LANGUAGE PLPGSQL;
13735
13736 -- Refresh the mat view with the corrected underlying view
13737 TRUNCATE money.materialized_billable_xact_summary;
13738 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13739
13740 -- Now redefine the view as a window onto the materialized view
13741 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13742     SELECT * FROM money.materialized_billable_xact_summary;
13743
13744 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13745     user_id    IN INTEGER,
13746     perm_code  IN TEXT
13747 )
13748 RETURNS SETOF INTEGER AS $$
13749 --
13750 -- Return a set of all the org units for which a given user has a given
13751 -- permission, granted directly (not through inheritance from a parent
13752 -- org unit).
13753 --
13754 -- The permissions apply to a minimum depth of the org unit hierarchy,
13755 -- for the org unit(s) to which the user is assigned.  (They also apply
13756 -- to the subordinates of those org units, but we don't report the
13757 -- subordinates here.)
13758 --
13759 -- For purposes of this function, the permission.usr_work_ou_map table
13760 -- defines which users belong to which org units.  I.e. we ignore the
13761 -- home_ou column of actor.usr.
13762 --
13763 -- The result set may contain duplicates, which should be eliminated
13764 -- by a DISTINCT clause.
13765 --
13766 DECLARE
13767     b_super       BOOLEAN;
13768     n_perm        INTEGER;
13769     n_min_depth   INTEGER;
13770     n_work_ou     INTEGER;
13771     n_curr_ou     INTEGER;
13772     n_depth       INTEGER;
13773     n_curr_depth  INTEGER;
13774 BEGIN
13775     --
13776     -- Check for superuser
13777     --
13778     SELECT INTO b_super
13779         super_user
13780     FROM
13781         actor.usr
13782     WHERE
13783         id = user_id;
13784     --
13785     IF NOT FOUND THEN
13786         return;             -- No user?  No permissions.
13787     ELSIF b_super THEN
13788         --
13789         -- Super user has all permissions everywhere
13790         --
13791         FOR n_work_ou IN
13792             SELECT
13793                 id
13794             FROM
13795                 actor.org_unit
13796             WHERE
13797                 parent_ou IS NULL
13798         LOOP
13799             RETURN NEXT n_work_ou;
13800         END LOOP;
13801         RETURN;
13802     END IF;
13803     --
13804     -- Translate the permission name
13805     -- to a numeric permission id
13806     --
13807     SELECT INTO n_perm
13808         id
13809     FROM
13810         permission.perm_list
13811     WHERE
13812         code = perm_code;
13813     --
13814     IF NOT FOUND THEN
13815         RETURN;               -- No such permission
13816     END IF;
13817     --
13818     -- Find the highest-level org unit (i.e. the minimum depth)
13819     -- to which the permission is applied for this user
13820     --
13821     -- This query is modified from the one in permission.usr_perms().
13822     --
13823     SELECT INTO n_min_depth
13824         min( depth )
13825     FROM    (
13826         SELECT depth
13827           FROM permission.usr_perm_map upm
13828          WHERE upm.usr = user_id
13829            AND (upm.perm = n_perm OR upm.perm = -1)
13830                     UNION
13831         SELECT  gpm.depth
13832           FROM  permission.grp_perm_map gpm
13833           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13834             AND gpm.grp IN (
13835                SELECT   (permission.grp_ancestors(
13836                     (SELECT profile FROM actor.usr WHERE id = user_id)
13837                 )).id
13838             )
13839                     UNION
13840         SELECT  p.depth
13841           FROM  permission.grp_perm_map p
13842           WHERE (p.perm = n_perm OR p.perm = -1)
13843             AND p.grp IN (
13844                 SELECT (permission.grp_ancestors(m.grp)).id
13845                 FROM   permission.usr_grp_map m
13846                 WHERE  m.usr = user_id
13847             )
13848     ) AS x;
13849     --
13850     IF NOT FOUND THEN
13851         RETURN;                -- No such permission for this user
13852     END IF;
13853     --
13854     -- Identify the org units to which the user is assigned.  Note that
13855     -- we pay no attention to the home_ou column in actor.usr.
13856     --
13857     FOR n_work_ou IN
13858         SELECT
13859             work_ou
13860         FROM
13861             permission.usr_work_ou_map
13862         WHERE
13863             usr = user_id
13864     LOOP            -- For each org unit to which the user is assigned
13865         --
13866         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13867         -- We take it on faith that this depth agrees with the actual hierarchy
13868         -- defined in actor.org_unit.
13869         --
13870         SELECT INTO n_depth
13871             type.depth
13872         FROM
13873             actor.org_unit_type type
13874                 INNER JOIN actor.org_unit ou
13875                     ON ( ou.ou_type = type.id )
13876         WHERE
13877             ou.id = n_work_ou;
13878         --
13879         IF NOT FOUND THEN
13880             CONTINUE;        -- Maybe raise exception?
13881         END IF;
13882         --
13883         -- Compare the depth of the work org unit to the
13884         -- minimum depth, and branch accordingly
13885         --
13886         IF n_depth = n_min_depth THEN
13887             --
13888             -- The org unit is at the right depth, so return it.
13889             --
13890             RETURN NEXT n_work_ou;
13891         ELSIF n_depth > n_min_depth THEN
13892             --
13893             -- Traverse the org unit tree toward the root,
13894             -- until you reach the minimum depth determined above
13895             --
13896             n_curr_depth := n_depth;
13897             n_curr_ou := n_work_ou;
13898             WHILE n_curr_depth > n_min_depth LOOP
13899                 SELECT INTO n_curr_ou
13900                     parent_ou
13901                 FROM
13902                     actor.org_unit
13903                 WHERE
13904                     id = n_curr_ou;
13905                 --
13906                 IF FOUND THEN
13907                     n_curr_depth := n_curr_depth - 1;
13908                 ELSE
13909                     --
13910                     -- This can happen only if the hierarchy defined in
13911                     -- actor.org_unit is corrupted, or out of sync with
13912                     -- the depths defined in actor.org_unit_type.
13913                     -- Maybe we should raise an exception here, instead
13914                     -- of silently ignoring the problem.
13915                     --
13916                     n_curr_ou = NULL;
13917                     EXIT;
13918                 END IF;
13919             END LOOP;
13920             --
13921             IF n_curr_ou IS NOT NULL THEN
13922                 RETURN NEXT n_curr_ou;
13923             END IF;
13924         ELSE
13925             --
13926             -- The permission applies only at a depth greater than the work org unit.
13927             -- Use connectby() to find all dependent org units at the specified depth.
13928             --
13929             FOR n_curr_ou IN
13930                 SELECT ou::INTEGER
13931                 FROM connectby(
13932                         'actor.org_unit',         -- table name
13933                         'id',                     -- key column
13934                         'parent_ou',              -- recursive foreign key
13935                         n_work_ou::TEXT,          -- id of starting point
13936                         (n_min_depth - n_depth)   -- max depth to search, relative
13937                     )                             --   to starting point
13938                     AS t(
13939                         ou text,            -- dependent org unit
13940                         parent_ou text,     -- (ignore)
13941                         level int           -- depth relative to starting point
13942                     )
13943                 WHERE
13944                     level = n_min_depth - n_depth
13945             LOOP
13946                 RETURN NEXT n_curr_ou;
13947             END LOOP;
13948         END IF;
13949         --
13950     END LOOP;
13951     --
13952     RETURN;
13953     --
13954 END;
13955 $$ LANGUAGE 'plpgsql';
13956
13957 ALTER TABLE acq.purchase_order
13958         ADD COLUMN cancel_reason INT
13959                 REFERENCES acq.cancel_reason( id )
13960             DEFERRABLE INITIALLY DEFERRED,
13961         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
13962
13963 -- Build the history table and lifecycle view
13964 -- for acq.purchase_order
13965
13966 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
13967
13968 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
13969
13970 ALTER TABLE acq.lineitem
13971         ADD COLUMN cancel_reason INT
13972                 REFERENCES acq.cancel_reason( id )
13973             DEFERRABLE INITIALLY DEFERRED,
13974         ADD COLUMN estimated_unit_price NUMERIC,
13975         ADD COLUMN claim_policy INT
13976                 REFERENCES acq.claim_policy
13977                 DEFERRABLE INITIALLY DEFERRED,
13978         ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
13979
13980 -- Build the history table and lifecycle view
13981 -- for acq.lineitem
13982
13983 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
13984 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
13985
13986 ALTER TABLE acq.lineitem_detail
13987         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
13988                                             DEFERRABLE INITIALLY DEFERRED;
13989
13990 ALTER TABLE acq.lineitem_detail
13991         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
13992
13993 ALTER TABLE acq.lineitem_detail
13994         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13995                 ON DELETE CASCADE
13996                 DEFERRABLE INITIALLY DEFERRED;
13997
13998 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
13999
14000 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14001         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
14002
14003 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14004         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
14005
14006 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14007
14008     use MARC::Record;
14009     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14010     use strict;
14011
14012     my $target_xml = shift;
14013     my $source_xml = shift;
14014     my $field_spec = shift;
14015
14016     my $target_r = MARC::Record->new_from_xml( $target_xml );
14017     my $source_r = MARC::Record->new_from_xml( $source_xml );
14018
14019     return $target_xml unless ($target_r && $source_r);
14020
14021     my @field_list = split(',', $field_spec);
14022
14023     my %fields;
14024     for my $f (@field_list) {
14025         $f =~ s/^\s*//; $f =~ s/\s*$//;
14026         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14027             my $field = $1;
14028             $field =~ s/\s+//;
14029             my $sf = $2;
14030             $sf =~ s/\s+//;
14031             my $match = $3;
14032             $match =~ s/^\s*//; $match =~ s/\s*$//;
14033             $fields{$field} = { sf => [ split('', $sf) ] };
14034             if ($match) {
14035                 my ($msf,$mre) = split('~', $match);
14036                 if (length($msf) > 0 and length($mre) > 0) {
14037                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14038                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14039                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14040                 }
14041             }
14042         }
14043     }
14044
14045     for my $f ( keys %fields) {
14046         if ( @{$fields{$f}{sf}} ) {
14047             for my $from_field ($source_r->field( $f )) {
14048                 my @tos = $target_r->field( $f );
14049                 if (!@tos) {
14050                     my @new_fields = map { $_->clone } $source_r->field( $f );
14051                     $target_r->insert_fields_ordered( @new_fields );
14052                 } else {
14053                     for my $to_field (@tos) {
14054                         if (exists($fields{$f}{match})) {
14055                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14056                         }
14057                         my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
14058                         $to_field->add_subfields( @new_sf );
14059                     }
14060                 }
14061             }
14062         } else {
14063             my @new_fields = map { $_->clone } $source_r->field( $f );
14064             $target_r->insert_fields_ordered( @new_fields );
14065         }
14066     }
14067
14068     $target_xml = $target_r->as_xml_record;
14069     $target_xml =~ s/^<\?.+?\?>$//mo;
14070     $target_xml =~ s/\n//sgo;
14071     $target_xml =~ s/>\s+</></sgo;
14072
14073     return $target_xml;
14074
14075 $_$ LANGUAGE PLPERLU;
14076
14077 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14078
14079     use MARC::Record;
14080     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14081     use strict;
14082
14083     my $xml = shift;
14084     my $r = MARC::Record->new_from_xml( $xml );
14085
14086     return $xml unless ($r);
14087
14088     my $field_spec = shift;
14089     my @field_list = split(',', $field_spec);
14090
14091     my %fields;
14092     for my $f (@field_list) {
14093         $f =~ s/^\s*//; $f =~ s/\s*$//;
14094         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14095             my $field = $1;
14096             $field =~ s/\s+//;
14097             my $sf = $2;
14098             $sf =~ s/\s+//;
14099             my $match = $3;
14100             $match =~ s/^\s*//; $match =~ s/\s*$//;
14101             $fields{$field} = { sf => [ split('', $sf) ] };
14102             if ($match) {
14103                 my ($msf,$mre) = split('~', $match);
14104                 if (length($msf) > 0 and length($mre) > 0) {
14105                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14106                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14107                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14108                 }
14109             }
14110         }
14111     }
14112
14113     for my $f ( keys %fields) {
14114         for my $to_field ($r->field( $f )) {
14115             if (exists($fields{$f}{match})) {
14116                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14117             }
14118
14119             if ( @{$fields{$f}{sf}} ) {
14120                 $to_field->delete_subfield(code => $fields{$f}{sf});
14121             } else {
14122                 $r->delete_field( $to_field );
14123             }
14124         }
14125     }
14126
14127     $xml = $r->as_xml_record;
14128     $xml =~ s/^<\?.+?\?>$//mo;
14129     $xml =~ s/\n//sgo;
14130     $xml =~ s/>\s+</></sgo;
14131
14132     return $xml;
14133
14134 $_$ LANGUAGE PLPERLU;
14135
14136 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14137     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
14138 $_$ LANGUAGE SQL;
14139
14140 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14141     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
14142 $_$ LANGUAGE SQL;
14143
14144 CREATE VIEW action.unfulfilled_hold_max_loop AS
14145         SELECT  hold,
14146                 max(count) AS max
14147         FROM    action.unfulfilled_hold_loops
14148         GROUP BY 1;
14149
14150 ALTER TABLE acq.lineitem_attr
14151         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
14152
14153 ALTER TABLE acq.lineitem_attr
14154         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14155                 ON DELETE CASCADE
14156                 DEFERRABLE INITIALLY DEFERRED;
14157
14158 ALTER TABLE acq.po_note
14159         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
14160
14161 CREATE TABLE vandelay.merge_profile (
14162     id              BIGSERIAL   PRIMARY KEY,
14163     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
14164     name            TEXT        NOT NULL,
14165     add_spec        TEXT,
14166     replace_spec    TEXT,
14167     strip_spec      TEXT,
14168     preserve_spec   TEXT,
14169     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14170     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))
14171 );
14172
14173 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14174 DECLARE
14175     attr        RECORD;
14176     attr_def    RECORD;
14177     eg_rec      RECORD;
14178     id_value    TEXT;
14179     exact_id    BIGINT;
14180 BEGIN
14181
14182     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14183
14184     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14185
14186     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14187         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14188
14189         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14190             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14191             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14192             IF exact_id IS NOT NULL THEN
14193                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14194             END IF;
14195         END IF;
14196     END IF;
14197
14198     IF exact_id IS NULL THEN
14199         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
14200
14201             -- All numbers? check for an id match
14202             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14203                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14204                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14205                 END LOOP;
14206             END IF;
14207
14208             -- Looks like an ISBN? check for an isbn match
14209             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14210                 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
14211                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14212                     IF FOUND THEN
14213                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14214                     END IF;
14215                 END LOOP;
14216
14217                 -- subcheck for isbn-as-tcn
14218                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14219                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14220                 END LOOP;
14221             END IF;
14222
14223             -- check for an OCLC tcn_value match
14224             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14225                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14226                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14227                 END LOOP;
14228             END IF;
14229
14230             -- check for a direct tcn_value match
14231             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14232                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14233             END LOOP;
14234
14235             -- check for a direct item barcode match
14236             FOR eg_rec IN
14237                     SELECT  DISTINCT b.*
14238                       FROM  biblio.record_entry b
14239                             JOIN asset.call_number cn ON (cn.record = b.id)
14240                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14241                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14242             LOOP
14243                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14244             END LOOP;
14245
14246         END LOOP;
14247     END IF;
14248
14249     RETURN NULL;
14250 END;
14251 $func$ LANGUAGE PLPGSQL;
14252
14253 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 $_$
14254     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14255 $_$ LANGUAGE SQL;
14256
14257 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14258 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14259 DECLARE
14260     output              vandelay.compile_profile%ROWTYPE;
14261     profile             vandelay.merge_profile%ROWTYPE;
14262     profile_tmpl        TEXT;
14263     profile_tmpl_owner  TEXT;
14264     add_rule            TEXT := '';
14265     strip_rule          TEXT := '';
14266     replace_rule        TEXT := '';
14267     preserve_rule       TEXT := '';
14268
14269 BEGIN
14270
14271     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14272     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14273
14274     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14275         SELECT  p.* INTO profile
14276           FROM  vandelay.merge_profile p
14277                 JOIN actor.org_unit u ON (u.id = p.owner)
14278           WHERE p.name = profile_tmpl
14279                 AND u.shortname = profile_tmpl_owner;
14280
14281         IF profile.id IS NOT NULL THEN
14282             add_rule := COALESCE(profile.add_spec,'');
14283             strip_rule := COALESCE(profile.strip_spec,'');
14284             replace_rule := COALESCE(profile.replace_spec,'');
14285             preserve_rule := COALESCE(profile.preserve_spec,'');
14286         END IF;
14287     END IF;
14288
14289     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
14290     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
14291     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
14292     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
14293
14294     output.add_rule := BTRIM(add_rule,',');
14295     output.replace_rule := BTRIM(replace_rule,',');
14296     output.strip_rule := BTRIM(strip_rule,',');
14297     output.preserve_rule := BTRIM(preserve_rule,',');
14298
14299     RETURN output;
14300 END;
14301 $_$ LANGUAGE PLPGSQL;
14302
14303 -- Template-based marc munging functions
14304 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14305 DECLARE
14306     merge_profile   vandelay.merge_profile%ROWTYPE;
14307     dyn_profile     vandelay.compile_profile%ROWTYPE;
14308     editor_string   TEXT;
14309     editor_id       INT;
14310     source_marc     TEXT;
14311     target_marc     TEXT;
14312     eg_marc         TEXT;
14313     replace_rule    TEXT;
14314     match_count     INT;
14315 BEGIN
14316
14317     SELECT  b.marc INTO eg_marc
14318       FROM  biblio.record_entry b
14319       WHERE b.id = eg_id
14320       LIMIT 1;
14321
14322     IF eg_marc IS NULL OR v_marc IS NULL THEN
14323         -- RAISE NOTICE 'no marc for template or bib record';
14324         RETURN FALSE;
14325     END IF;
14326
14327     dyn_profile := vandelay.compile_profile( v_marc );
14328
14329     IF merge_profile_id IS NOT NULL THEN
14330         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14331         IF FOUND THEN
14332             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14333             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14334             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14335             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14336         END IF;
14337     END IF;
14338
14339     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14340         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14341         RETURN FALSE;
14342     END IF;
14343
14344     IF dyn_profile.replace_rule <> '' THEN
14345         source_marc = v_marc;
14346         target_marc = eg_marc;
14347         replace_rule = dyn_profile.replace_rule;
14348     ELSE
14349         source_marc = eg_marc;
14350         target_marc = v_marc;
14351         replace_rule = dyn_profile.preserve_rule;
14352     END IF;
14353
14354     UPDATE  biblio.record_entry
14355       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14356       WHERE id = eg_id;
14357
14358     IF NOT FOUND THEN
14359         -- RAISE NOTICE 'update of biblio.record_entry failed';
14360         RETURN FALSE;
14361     END IF;
14362
14363     RETURN TRUE;
14364
14365 END;
14366 $$ LANGUAGE PLPGSQL;
14367
14368 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14369     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14370 $$ LANGUAGE SQL;
14371
14372 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14373 DECLARE
14374     merge_profile   vandelay.merge_profile%ROWTYPE;
14375     dyn_profile     vandelay.compile_profile%ROWTYPE;
14376     editor_string   TEXT;
14377     editor_id       INT;
14378     source_marc     TEXT;
14379     target_marc     TEXT;
14380     eg_marc         TEXT;
14381     v_marc          TEXT;
14382     replace_rule    TEXT;
14383     match_count     INT;
14384 BEGIN
14385
14386     SELECT  q.marc INTO v_marc
14387       FROM  vandelay.queued_record q
14388             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14389       LIMIT 1;
14390
14391     IF v_marc IS NULL THEN
14392         -- RAISE NOTICE 'no marc for vandelay or bib record';
14393         RETURN FALSE;
14394     END IF;
14395
14396     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14397         UPDATE  vandelay.queued_bib_record
14398           SET   imported_as = eg_id,
14399                 import_time = NOW()
14400           WHERE id = import_id;
14401
14402         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14403
14404         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14405             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14406
14407             IF editor_id IS NULL THEN
14408                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14409             END IF;
14410
14411             IF editor_id IS NOT NULL THEN
14412                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14413             END IF;
14414         END IF;
14415
14416         RETURN TRUE;
14417     END IF;
14418
14419     -- RAISE NOTICE 'update of biblio.record_entry failed';
14420
14421     RETURN FALSE;
14422
14423 END;
14424 $$ LANGUAGE PLPGSQL;
14425
14426 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14427 DECLARE
14428     eg_id           BIGINT;
14429     match_count     INT;
14430     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14431 BEGIN
14432
14433     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14434
14435     IF FOUND THEN
14436         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14437         RETURN FALSE;
14438     END IF;
14439
14440     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14441
14442     IF match_count <> 1 THEN
14443         -- RAISE NOTICE 'not an exact match';
14444         RETURN FALSE;
14445     END IF;
14446
14447     SELECT  d.* INTO match_attr
14448       FROM  vandelay.bib_attr_definition d
14449             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14450             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14451       WHERE m.queued_record = import_id;
14452
14453     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14454         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14455         RETURN FALSE;
14456     END IF;
14457
14458     SELECT  m.eg_record INTO eg_id
14459       FROM  vandelay.bib_match m
14460       WHERE m.queued_record = import_id
14461       LIMIT 1;
14462
14463     IF eg_id IS NULL THEN
14464         RETURN FALSE;
14465     END IF;
14466
14467     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14468 END;
14469 $$ LANGUAGE PLPGSQL;
14470
14471 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14472 DECLARE
14473     queued_record   vandelay.queued_bib_record%ROWTYPE;
14474 BEGIN
14475
14476     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14477
14478         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14479             RETURN NEXT queued_record.id;
14480         END IF;
14481
14482     END LOOP;
14483
14484     RETURN;
14485
14486 END;
14487 $$ LANGUAGE PLPGSQL;
14488
14489 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14490     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14491 $$ LANGUAGE SQL;
14492
14493 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14494 DECLARE
14495     merge_profile   vandelay.merge_profile%ROWTYPE;
14496     dyn_profile     vandelay.compile_profile%ROWTYPE;
14497     source_marc     TEXT;
14498     target_marc     TEXT;
14499     eg_marc         TEXT;
14500     v_marc          TEXT;
14501     replace_rule    TEXT;
14502     match_count     INT;
14503 BEGIN
14504
14505     SELECT  b.marc INTO eg_marc
14506       FROM  authority.record_entry b
14507             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14508       LIMIT 1;
14509
14510     SELECT  q.marc INTO v_marc
14511       FROM  vandelay.queued_record q
14512             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14513       LIMIT 1;
14514
14515     IF eg_marc IS NULL OR v_marc IS NULL THEN
14516         -- RAISE NOTICE 'no marc for vandelay or authority record';
14517         RETURN FALSE;
14518     END IF;
14519
14520     dyn_profile := vandelay.compile_profile( v_marc );
14521
14522     IF merge_profile_id IS NOT NULL THEN
14523         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14524         IF FOUND THEN
14525             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14526             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14527             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14528             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14529         END IF;
14530     END IF;
14531
14532     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14533         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14534         RETURN FALSE;
14535     END IF;
14536
14537     IF dyn_profile.replace_rule <> '' THEN
14538         source_marc = v_marc;
14539         target_marc = eg_marc;
14540         replace_rule = dyn_profile.replace_rule;
14541     ELSE
14542         source_marc = eg_marc;
14543         target_marc = v_marc;
14544         replace_rule = dyn_profile.preserve_rule;
14545     END IF;
14546
14547     UPDATE  authority.record_entry
14548       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14549       WHERE id = eg_id;
14550
14551     IF FOUND THEN
14552         UPDATE  vandelay.queued_authority_record
14553           SET   imported_as = eg_id,
14554                 import_time = NOW()
14555           WHERE id = import_id;
14556         RETURN TRUE;
14557     END IF;
14558
14559     -- RAISE NOTICE 'update of authority.record_entry failed';
14560
14561     RETURN FALSE;
14562
14563 END;
14564 $$ LANGUAGE PLPGSQL;
14565
14566 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14567 DECLARE
14568     eg_id           BIGINT;
14569     match_count     INT;
14570 BEGIN
14571     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14572
14573     IF match_count <> 1 THEN
14574         -- RAISE NOTICE 'not an exact match';
14575         RETURN FALSE;
14576     END IF;
14577
14578     SELECT  m.eg_record INTO eg_id
14579       FROM  vandelay.authority_match m
14580       WHERE m.queued_record = import_id
14581       LIMIT 1;
14582
14583     IF eg_id IS NULL THEN
14584         RETURN FALSE;
14585     END IF;
14586
14587     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14588 END;
14589 $$ LANGUAGE PLPGSQL;
14590
14591 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14592 DECLARE
14593     queued_record   vandelay.queued_authority_record%ROWTYPE;
14594 BEGIN
14595
14596     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14597
14598         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14599             RETURN NEXT queued_record.id;
14600         END IF;
14601
14602     END LOOP;
14603
14604     RETURN;
14605
14606 END;
14607 $$ LANGUAGE PLPGSQL;
14608
14609 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14610     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14611 $$ LANGUAGE SQL;
14612
14613 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14614 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14615 DECLARE
14616     eg_tcn          TEXT;
14617     eg_tcn_source   TEXT;
14618     output          vandelay.tcn_data%ROWTYPE;
14619 BEGIN
14620
14621     -- 001/003
14622     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14623     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14624
14625         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14626         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14627             eg_tcn_source := 'System Local';
14628         END IF;
14629
14630         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14631
14632         IF NOT FOUND THEN
14633             output.used := FALSE;
14634         ELSE
14635             output.used := TRUE;
14636         END IF;
14637
14638         output.tcn := eg_tcn;
14639         output.tcn_source := eg_tcn_source;
14640         RETURN NEXT output;
14641
14642     END IF;
14643
14644     -- 901 ab
14645     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14646     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14647
14648         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14649         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14650             eg_tcn_source := 'System Local';
14651         END IF;
14652
14653         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14654
14655         IF NOT FOUND THEN
14656             output.used := FALSE;
14657         ELSE
14658             output.used := TRUE;
14659         END IF;
14660
14661         output.tcn := eg_tcn;
14662         output.tcn_source := eg_tcn_source;
14663         RETURN NEXT output;
14664
14665     END IF;
14666
14667     -- 039 ab
14668     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14669     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14670
14671         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14672         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14673             eg_tcn_source := 'System Local';
14674         END IF;
14675
14676         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14677
14678         IF NOT FOUND THEN
14679             output.used := FALSE;
14680         ELSE
14681             output.used := TRUE;
14682         END IF;
14683
14684         output.tcn := eg_tcn;
14685         output.tcn_source := eg_tcn_source;
14686         RETURN NEXT output;
14687
14688     END IF;
14689
14690     -- 020 a
14691     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14692     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14693
14694         eg_tcn_source := 'ISBN';
14695
14696         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14697
14698         IF NOT FOUND THEN
14699             output.used := FALSE;
14700         ELSE
14701             output.used := TRUE;
14702         END IF;
14703
14704         output.tcn := eg_tcn;
14705         output.tcn_source := eg_tcn_source;
14706         RETURN NEXT output;
14707
14708     END IF;
14709
14710     -- 022 a
14711     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14712     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14713
14714         eg_tcn_source := 'ISSN';
14715
14716         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14717
14718         IF NOT FOUND THEN
14719             output.used := FALSE;
14720         ELSE
14721             output.used := TRUE;
14722         END IF;
14723
14724         output.tcn := eg_tcn;
14725         output.tcn_source := eg_tcn_source;
14726         RETURN NEXT output;
14727
14728     END IF;
14729
14730     -- 010 a
14731     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14732     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14733
14734         eg_tcn_source := 'LCCN';
14735
14736         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14737
14738         IF NOT FOUND THEN
14739             output.used := FALSE;
14740         ELSE
14741             output.used := TRUE;
14742         END IF;
14743
14744         output.tcn := eg_tcn;
14745         output.tcn_source := eg_tcn_source;
14746         RETURN NEXT output;
14747
14748     END IF;
14749
14750     -- 035 a
14751     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
14752     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14753
14754         eg_tcn_source := 'System Legacy';
14755
14756         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14757
14758         IF NOT FOUND THEN
14759             output.used := FALSE;
14760         ELSE
14761             output.used := TRUE;
14762         END IF;
14763
14764         output.tcn := eg_tcn;
14765         output.tcn_source := eg_tcn_source;
14766         RETURN NEXT output;
14767
14768     END IF;
14769
14770     RETURN;
14771 END;
14772 $_$ LANGUAGE PLPGSQL;
14773
14774 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14775
14776 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);
14777
14778 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
14779
14780 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14781 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14782 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14783 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14784 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14785
14786 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14787 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14788 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14789 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14790
14791 ALTER TABLE metabib.series_field_entry
14792         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14793                 REFERENCES biblio.record_entry (id)
14794                 ON DELETE CASCADE
14795                 DEFERRABLE INITIALLY DEFERRED;
14796
14797 ALTER TABLE metabib.series_field_entry
14798         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14799                 REFERENCES config.metabib_field (id)
14800                 ON DELETE CASCADE
14801                 DEFERRABLE INITIALLY DEFERRED;
14802
14803 CREATE TABLE acq.claim_policy_action (
14804         id              SERIAL       PRIMARY KEY,
14805         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14806                                  ON DELETE CASCADE
14807                                      DEFERRABLE INITIALLY DEFERRED,
14808         action_interval INTERVAL     NOT NULL,
14809         action          INT          NOT NULL REFERENCES acq.claim_event_type
14810                                      DEFERRABLE INITIALLY DEFERRED,
14811         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14812 );
14813
14814 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14815 DECLARE
14816     value       TEXT;
14817     atype       TEXT;
14818     prov        INT;
14819     pos         INT;
14820     adef        RECORD;
14821     xpath_string    TEXT;
14822 BEGIN
14823     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14824  
14825         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14826  
14827         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14828             IF (atype = 'lineitem_provider_attr_definition') THEN
14829                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14830                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14831             END IF;
14832  
14833             IF (atype = 'lineitem_provider_attr_definition') THEN
14834                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14835             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14836                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14837             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14838                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14839             END IF;
14840  
14841             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14842  
14843             pos := 1;
14844  
14845             LOOP
14846                 SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14847  
14848                 IF (value IS NOT NULL AND value <> '') THEN
14849                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14850                         VALUES (NEW.id, adef.id, atype, adef.code, value);
14851                 ELSE
14852                     EXIT;
14853                 END IF;
14854  
14855                 pos := pos + 1;
14856             END LOOP;
14857  
14858         END IF;
14859  
14860     END LOOP;
14861  
14862     RETURN NULL;
14863 END;
14864 $function$ LANGUAGE PLPGSQL;
14865
14866 UPDATE config.metabib_field SET label = name;
14867 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14868
14869 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
14870          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
14871
14872 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
14873
14874 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
14875
14876 CREATE TABLE config.metabib_search_alias (
14877     alias       TEXT    PRIMARY KEY,
14878     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
14879     field       INT     REFERENCES config.metabib_field (id)
14880 );
14881
14882 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
14883 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
14884 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
14885 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
14886 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
14887 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
14888 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
14889 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
14890
14891 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
14892 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
14893 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
14894 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
14895 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
14896 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
14897 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
14898 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
14899 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
14900 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
14901 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
14902 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
14903 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
14904
14905 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
14906 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
14907 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
14908 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
14909 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
14910 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
14911 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
14912 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
14913
14914 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
14915 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
14916 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
14917 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
14918 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
14919 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
14920
14921 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
14922 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
14923 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
14924
14925 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
14926 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;
14927 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;
14928 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;
14929 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;
14930
14931 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
14932 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
14933 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
14934 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
14935
14936 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
14937 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
14938 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
14939 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
14940 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
14941 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
14942 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
14943 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
14944 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
14945 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
14946 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
14947 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
14948 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
14949 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
14950
14951 CREATE TABLE asset.opac_visible_copies (
14952   id        BIGINT primary key, -- copy id
14953   record    BIGINT,
14954   circ_lib  INTEGER
14955 );
14956 COMMENT ON TABLE asset.opac_visible_copies IS $$
14957 Materialized view of copies that are visible in the OPAC, used by
14958 search.query_parser_fts() to speed up OPAC visibility checks on large
14959 databases.  Contents are maintained by a set of triggers.
14960 $$;
14961 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
14962
14963 CREATE OR REPLACE FUNCTION search.query_parser_fts (
14964
14965     param_search_ou INT,
14966     param_depth     INT,
14967     param_query     TEXT,
14968     param_statuses  INT[],
14969     param_locations INT[],
14970     param_offset    INT,
14971     param_check     INT,
14972     param_limit     INT,
14973     metarecord      BOOL,
14974     staff           BOOL
14975  
14976 ) RETURNS SETOF search.search_result AS $func$
14977 DECLARE
14978
14979     current_res         search.search_result%ROWTYPE;
14980     search_org_list     INT[];
14981
14982     check_limit         INT;
14983     core_limit          INT;
14984     core_offset         INT;
14985     tmp_int             INT;
14986
14987     core_result         RECORD;
14988     core_cursor         REFCURSOR;
14989     core_rel_query      TEXT;
14990
14991     total_count         INT := 0;
14992     check_count         INT := 0;
14993     deleted_count       INT := 0;
14994     visible_count       INT := 0;
14995     excluded_count      INT := 0;
14996
14997 BEGIN
14998
14999     check_limit := COALESCE( param_check, 1000 );
15000     core_limit  := COALESCE( param_limit, 25000 );
15001     core_offset := COALESCE( param_offset, 0 );
15002
15003     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
15004
15005     IF param_search_ou > 0 THEN
15006         IF param_depth IS NOT NULL THEN
15007             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
15008         ELSE
15009             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
15010         END IF;
15011     ELSIF param_search_ou < 0 THEN
15012         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
15013     ELSIF param_search_ou = 0 THEN
15014         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
15015     END IF;
15016
15017     OPEN core_cursor FOR EXECUTE param_query;
15018
15019     LOOP
15020
15021         FETCH core_cursor INTO core_result;
15022         EXIT WHEN NOT FOUND;
15023         EXIT WHEN total_count >= core_limit;
15024
15025         total_count := total_count + 1;
15026
15027         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
15028
15029         check_count := check_count + 1;
15030
15031         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15032         IF NOT FOUND THEN
15033             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
15034             deleted_count := deleted_count + 1;
15035             CONTINUE;
15036         END IF;
15037
15038         PERFORM 1
15039           FROM  biblio.record_entry b
15040                 JOIN config.bib_source s ON (b.source = s.id)
15041           WHERE s.transcendant
15042                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15043
15044         IF FOUND THEN
15045             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
15046             visible_count := visible_count + 1;
15047
15048             current_res.id = core_result.id;
15049             current_res.rel = core_result.rel;
15050
15051             tmp_int := 1;
15052             IF metarecord THEN
15053                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15054             END IF;
15055
15056             IF tmp_int = 1 THEN
15057                 current_res.record = core_result.records[1];
15058             ELSE
15059                 current_res.record = NULL;
15060             END IF;
15061
15062             RETURN NEXT current_res;
15063
15064             CONTINUE;
15065         END IF;
15066
15067         PERFORM 1
15068           FROM  asset.call_number cn
15069                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
15070                 JOIN asset.uri uri ON (map.uri = uri.id)
15071           WHERE NOT cn.deleted
15072                 AND cn.label = '##URI##'
15073                 AND uri.active
15074                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
15075                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15076                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15077           LIMIT 1;
15078
15079         IF FOUND THEN
15080             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
15081             visible_count := visible_count + 1;
15082
15083             current_res.id = core_result.id;
15084             current_res.rel = core_result.rel;
15085
15086             tmp_int := 1;
15087             IF metarecord THEN
15088                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15089             END IF;
15090
15091             IF tmp_int = 1 THEN
15092                 current_res.record = core_result.records[1];
15093             ELSE
15094                 current_res.record = NULL;
15095             END IF;
15096
15097             RETURN NEXT current_res;
15098
15099             CONTINUE;
15100         END IF;
15101
15102         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
15103
15104             PERFORM 1
15105               FROM  asset.call_number cn
15106                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15107               WHERE NOT cn.deleted
15108                     AND NOT cp.deleted
15109                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
15110                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15111                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15112               LIMIT 1;
15113
15114             IF NOT FOUND THEN
15115                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
15116                 excluded_count := excluded_count + 1;
15117                 CONTINUE;
15118             END IF;
15119
15120         END IF;
15121
15122         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
15123
15124             PERFORM 1
15125               FROM  asset.call_number cn
15126                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15127               WHERE NOT cn.deleted
15128                     AND NOT cp.deleted
15129                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
15130                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15131                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15132               LIMIT 1;
15133
15134             IF NOT FOUND THEN
15135                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
15136                 excluded_count := excluded_count + 1;
15137                 CONTINUE;
15138             END IF;
15139
15140         END IF;
15141
15142         IF staff IS NULL OR NOT staff THEN
15143
15144             PERFORM 1
15145               FROM  asset.opac_visible_copies
15146               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15147                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15148               LIMIT 1;
15149
15150             IF NOT FOUND THEN
15151                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15152                 excluded_count := excluded_count + 1;
15153                 CONTINUE;
15154             END IF;
15155
15156         ELSE
15157
15158             PERFORM 1
15159               FROM  asset.call_number cn
15160                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15161                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15162               WHERE NOT cn.deleted
15163                     AND NOT cp.deleted
15164                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15165                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15166               LIMIT 1;
15167
15168             IF NOT FOUND THEN
15169
15170                 PERFORM 1
15171                   FROM  asset.call_number cn
15172                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15173                   LIMIT 1;
15174
15175                 IF FOUND THEN
15176                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15177                     excluded_count := excluded_count + 1;
15178                     CONTINUE;
15179                 END IF;
15180
15181             END IF;
15182
15183         END IF;
15184
15185         visible_count := visible_count + 1;
15186
15187         current_res.id = core_result.id;
15188         current_res.rel = core_result.rel;
15189
15190         tmp_int := 1;
15191         IF metarecord THEN
15192             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15193         END IF;
15194
15195         IF tmp_int = 1 THEN
15196             current_res.record = core_result.records[1];
15197         ELSE
15198             current_res.record = NULL;
15199         END IF;
15200
15201         RETURN NEXT current_res;
15202
15203         IF visible_count % 1000 = 0 THEN
15204             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15205         END IF;
15206
15207     END LOOP;
15208
15209     current_res.id = NULL;
15210     current_res.rel = NULL;
15211     current_res.record = NULL;
15212     current_res.total = total_count;
15213     current_res.checked = check_count;
15214     current_res.deleted = deleted_count;
15215     current_res.visible = visible_count;
15216     current_res.excluded = excluded_count;
15217
15218     CLOSE core_cursor;
15219
15220     RETURN NEXT current_res;
15221
15222 END;
15223 $func$ LANGUAGE PLPGSQL;
15224
15225 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15226 ALTER TABLE biblio.record_entry
15227          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15228          REFERENCES actor.org_unit (id)
15229          DEFERRABLE INITIALLY DEFERRED;
15230
15231 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15232
15233 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15234 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15235
15236 DROP VIEW auditor.biblio_record_entry_lifecycle;
15237
15238 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15239
15240 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15241         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15242 $$ LANGUAGE SQL STRICT IMMUTABLE;
15243
15244 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15245     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15246 $$ LANGUAGE SQL STRICT IMMUTABLE;
15247
15248 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15249     return lc(shift);
15250 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15251
15252 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15253     return uc(shift);
15254 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15255
15256 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15257     use Unicode::Normalize;
15258
15259     my $x = NFD(shift);
15260     $x =~ s/\pM+//go;
15261     return $x;
15262
15263 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15264
15265 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15266     use Unicode::Normalize;
15267
15268     my $x = NFC(shift);
15269     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15270     return $x;
15271
15272 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15273
15274 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15275 DECLARE
15276     setting RECORD;
15277     cur_org INT;
15278 BEGIN
15279     cur_org := org_id;
15280     LOOP
15281         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15282         IF FOUND THEN
15283             RETURN NEXT setting;
15284         END IF;
15285         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15286         EXIT WHEN cur_org IS NULL;
15287     END LOOP;
15288     RETURN;
15289 END;
15290 $$ LANGUAGE plpgsql STABLE;
15291
15292 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15293 DECLARE
15294     counter INT;
15295     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15296 BEGIN
15297
15298     SELECT  COUNT(*) INTO counter
15299       FROM  oils_xpath_table(
15300                 'id',
15301                 'marc',
15302                 'acq.lineitem',
15303                 '//*[@tag="' || tag || '"]',
15304                 'id=' || lineitem
15305             ) as t(i int,c text);
15306
15307     FOR i IN 1 .. counter LOOP
15308         FOR lida IN
15309             SELECT  *
15310               FROM  (   SELECT  id,i,t,v
15311                           FROM  oils_xpath_table(
15312                                     'id',
15313                                     'marc',
15314                                     'acq.lineitem',
15315                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15316                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15317                                     'id=' || lineitem
15318                                 ) as t(id int,t text,v text)
15319                     )x
15320         LOOP
15321             RETURN NEXT lida;
15322         END LOOP;
15323     END LOOP;
15324
15325     RETURN;
15326 END;
15327 $$ LANGUAGE PLPGSQL;
15328
15329 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15330 DECLARE
15331     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15332     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15333     result      config.i18n_core%ROWTYPE;
15334     fallback    TEXT;
15335     keyfield    TEXT := keyclass || '.' || keycol;
15336 BEGIN
15337
15338     -- Try the full locale
15339     SELECT  * INTO result
15340       FROM  config.i18n_core
15341       WHERE fq_field = keyfield
15342             AND identity_value = keyvalue
15343             AND translation = locale;
15344
15345     -- Try just the language
15346     IF NOT FOUND THEN
15347         SELECT  * INTO result
15348           FROM  config.i18n_core
15349           WHERE fq_field = keyfield
15350                 AND identity_value = keyvalue
15351                 AND translation = language;
15352     END IF;
15353
15354     -- Fall back to the string we passed in in the first place
15355     IF NOT FOUND THEN
15356     EXECUTE
15357             'SELECT ' ||
15358                 keycol ||
15359             ' FROM ' || keytable ||
15360             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15361                 INTO fallback;
15362         RETURN fallback;
15363     END IF;
15364
15365     RETURN result.string;
15366 END;
15367 $func$ LANGUAGE PLPGSQL STABLE;
15368
15369 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15370
15371 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15372
15373 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15374
15375 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15376     3, 1, 'delivered_but_lost',
15377     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15378
15379 CREATE TABLE config.global_flag (
15380     label   TEXT    NOT NULL
15381 ) INHERITS (config.internal_flag);
15382 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15383
15384 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
15385     VALUES (
15386         'cat.bib.use_id_for_tcn',
15387         oils_i18n_gettext(
15388             'cat.bib.use_id_for_tcn',
15389             'Cat: Use Internal ID for TCN Value',
15390             'cgf', 
15391             'label'
15392         )
15393     );
15394
15395 -- resolves performance issue noted by EG Indiana
15396
15397 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15398
15399 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15400
15401 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15402     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15403 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15404     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15405 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15406     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15407 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15408     (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 );
15409 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15410     (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 );
15411 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15412     (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 );
15413 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15414     (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 );
15415 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15416     (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 );
15417 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15418     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15419
15420 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15421  
15422
15423 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15424
15425 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15426 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15427 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15428 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15429 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15430 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15431
15432 CREATE TABLE metabib.identifier_field_entry (
15433         id              BIGSERIAL       PRIMARY KEY,
15434         source          BIGINT          NOT NULL,
15435         field           INT             NOT NULL,
15436         value           TEXT            NOT NULL,
15437         index_vector    tsvector        NOT NULL
15438 );
15439 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15440         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15441         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15442
15443 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15444 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15445     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15446 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15447
15448 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15449     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15450 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15451     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15452
15453 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15454     use Business::ISBN;
15455     use strict;
15456     use warnings;
15457
15458     # For each ISBN found in a single string containing a set of ISBNs:
15459     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15460     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15461
15462     my $input = shift;
15463     my $output = '';
15464
15465     foreach my $word (split(/\s/, $input)) {
15466         my $isbn = Business::ISBN->new($word);
15467
15468         # First check the checksum; if it is not valid, fix it and add the original
15469         # bad-checksum ISBN to the output
15470         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15471             $output .= $isbn->isbn() . " ";
15472             $isbn->fix_checksum();
15473         }
15474
15475         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15476         # and add the normalized original ISBN to the output
15477         if ($isbn && $isbn->is_valid()) {
15478             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15479             $output .= $isbn->isbn . " ";
15480
15481             # If we successfully converted the ISBN to its counterpart, add the
15482             # converted ISBN to the output as well
15483             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15484         }
15485     }
15486     return $output if $output;
15487
15488     # If there were no valid ISBNs, just return the raw input
15489     return $input;
15490 $func$ LANGUAGE PLPERLU;
15491
15492 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15493 /*
15494  * Copyright (C) 2010 Merrimack Valley Library Consortium
15495  * Jason Stephenson <jstephenson@mvlc.org>
15496  * Copyright (C) 2010 Laurentian University
15497  * Dan Scott <dscott@laurentian.ca>
15498  *
15499  * The translate_isbn1013 function takes an input ISBN and returns the
15500  * following in a single space-delimited string if the input ISBN is valid:
15501  *   - The normalized input ISBN (hyphens stripped)
15502  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15503  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15504  */
15505 $$;
15506
15507 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15508 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15509 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15510 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15511 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15512 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15513
15514 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15515         'ISBN 10/13 conversion',
15516         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15517         'translate_isbn1013',
15518         0
15519 );
15520
15521 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15522         'Replace',
15523         'Replace all occurences of first parameter in the string with the second parameter.',
15524         'replace',
15525         2
15526 );
15527
15528 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15529     SELECT  m.id, i.id, 1
15530       FROM  config.metabib_field m,
15531             config.index_normalizer i
15532       WHERE i.func IN ('first_word')
15533             AND m.id IN (18);
15534
15535 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15536     SELECT  m.id, i.id, 2
15537       FROM  config.metabib_field m,
15538             config.index_normalizer i
15539       WHERE i.func IN ('translate_isbn1013')
15540             AND m.id IN (18);
15541
15542 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15543     SELECT  m.id, i.id, $$['-','']$$
15544       FROM  config.metabib_field m,
15545             config.index_normalizer i
15546       WHERE i.func IN ('replace')
15547             AND m.id IN (19);
15548
15549 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15550     SELECT  m.id, i.id, $$[' ','']$$
15551       FROM  config.metabib_field m,
15552             config.index_normalizer i
15553       WHERE i.func IN ('replace')
15554             AND m.id IN (19);
15555
15556 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15557
15558 UPDATE  config.metabib_field_index_norm_map
15559   SET   params = REPLACE(params,E'\'','"')
15560   WHERE params IS NOT NULL AND params <> '';
15561
15562 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15563
15564 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15565
15566 ALTER TABLE config.circ_modifier
15567         ADD COLUMN avg_wait_time INTERVAL;
15568
15569 --CREATE TABLE actor.usr_password_reset (
15570 --  id SERIAL PRIMARY KEY,
15571 --  uuid TEXT NOT NULL, 
15572 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15573 --  request_time TIMESTAMP NOT NULL DEFAULT NOW(), 
15574 --  has_been_reset BOOL NOT NULL DEFAULT false
15575 --);
15576 --COMMENT ON TABLE actor.usr_password_reset IS $$
15577 --/*
15578 -- * Copyright (C) 2010 Laurentian University
15579 -- * Dan Scott <dscott@laurentian.ca>
15580 -- *
15581 -- * Self-serve password reset requests
15582 -- *
15583 -- * ****
15584 -- *
15585 -- * This program is free software; you can redistribute it and/or
15586 -- * modify it under the terms of the GNU General Public License
15587 -- * as published by the Free Software Foundation; either version 2
15588 -- * of the License, or (at your option) any later version.
15589 -- *
15590 -- * This program is distributed in the hope that it will be useful,
15591 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15592 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15593 -- * GNU General Public License for more details.
15594 -- */
15595 --$$;
15596 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15597 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15598 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15599 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15600
15601 -- Use the identifier search class tsconfig
15602 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15603 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15604     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15605     FOR EACH ROW
15606     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15607
15608 INSERT INTO config.global_flag (name,label,enabled)
15609     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15610 INSERT INTO config.global_flag (name,label,enabled)
15611     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15612
15613 -- turn a JSON scalar into an SQL TEXT value
15614 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15615     use JSON::XS;                    
15616     my $json = shift();
15617     my $txt;
15618     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15619     return undef if ($@);
15620     return $txt
15621 $f$ LANGUAGE PLPERLU;
15622
15623 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15624 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15625 DECLARE
15626     c               action.circulation%ROWTYPE;
15627     view_age        INTERVAL;
15628     usr_view_age    actor.usr_setting%ROWTYPE;
15629     usr_view_start  actor.usr_setting%ROWTYPE;
15630 BEGIN
15631     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15632     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15633
15634     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15635         -- User opted in and supplied a retention age
15636         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15637             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15638         ELSE
15639             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15640         END IF;
15641     ELSIF usr_view_start.value IS NOT NULL THEN
15642         -- User opted in
15643         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15644     ELSE
15645         -- User did not opt in
15646         RETURN;
15647     END IF;
15648
15649     FOR c IN
15650         SELECT  *
15651           FROM  action.circulation
15652           WHERE usr = usr_id
15653                 AND parent_circ IS NULL
15654                 AND xact_start > NOW() - view_age
15655           ORDER BY xact_start
15656     LOOP
15657         RETURN NEXT c;
15658     END LOOP;
15659
15660     RETURN;
15661 END;
15662 $func$ LANGUAGE PLPGSQL;
15663
15664 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15665 DECLARE
15666     usr_keep_age    actor.usr_setting%ROWTYPE;
15667     usr_keep_start  actor.usr_setting%ROWTYPE;
15668     org_keep_age    INTERVAL;
15669     org_keep_count  INT;
15670
15671     keep_age        INTERVAL;
15672
15673     target_acp      RECORD;
15674     circ_chain_head action.circulation%ROWTYPE;
15675     circ_chain_tail action.circulation%ROWTYPE;
15676
15677     purge_position  INT;
15678     count_purged    INT;
15679 BEGIN
15680
15681     count_purged := 0;
15682
15683     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15684
15685     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15686     IF org_keep_count IS NULL THEN
15687         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15688     END IF;
15689
15690     -- First, find copies with more than keep_count non-renewal circs
15691     FOR target_acp IN
15692         SELECT  target_copy,
15693                 COUNT(*) AS total_real_circs
15694           FROM  action.circulation
15695           WHERE parent_circ IS NULL
15696                 AND xact_finish IS NOT NULL
15697           GROUP BY target_copy
15698           HAVING COUNT(*) > org_keep_count
15699     LOOP
15700         purge_position := 0;
15701         -- And, for those, select circs that are finished and older than keep_age
15702         FOR circ_chain_head IN
15703             SELECT  *
15704               FROM  action.circulation
15705               WHERE target_copy = target_acp.target_copy
15706                     AND parent_circ IS NULL
15707               ORDER BY xact_start
15708         LOOP
15709
15710             -- Stop once we've purged enough circs to hit org_keep_count
15711             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15712
15713             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15714             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15715
15716             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15717             usr_keep_age.value := NULL;
15718             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15719
15720             usr_keep_start.value := NULL;
15721             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15722
15723             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15724                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15725                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15726                 ELSE
15727                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15728                 END IF;
15729             ELSIF usr_keep_start.value IS NOT NULL THEN
15730                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15731             ELSE
15732                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15733             END IF;
15734
15735             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15736
15737             -- We've passed the purging tests, purge the circ chain starting at the end
15738             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15739             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15740                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15741                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15742             END LOOP;
15743
15744             count_purged := count_purged + 1;
15745             purge_position := purge_position + 1;
15746
15747         END LOOP;
15748     END LOOP;
15749 END;
15750 $func$ LANGUAGE PLPGSQL;
15751
15752 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15753 DECLARE
15754     h               action.hold_request%ROWTYPE;
15755     view_age        INTERVAL;
15756     view_count      INT;
15757     usr_view_count  actor.usr_setting%ROWTYPE;
15758     usr_view_age    actor.usr_setting%ROWTYPE;
15759     usr_view_start  actor.usr_setting%ROWTYPE;
15760 BEGIN
15761     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15762     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15763     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15764
15765     FOR h IN
15766         SELECT  *
15767           FROM  action.hold_request
15768           WHERE usr = usr_id
15769                 AND fulfillment_time IS NULL
15770                 AND cancel_time IS NULL
15771           ORDER BY request_time DESC
15772     LOOP
15773         RETURN NEXT h;
15774     END LOOP;
15775
15776     IF usr_view_start.value IS NULL THEN
15777         RETURN;
15778     END IF;
15779
15780     IF usr_view_age.value IS NOT NULL THEN
15781         -- User opted in and supplied a retention age
15782         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15783             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15784         ELSE
15785             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15786         END IF;
15787     ELSE
15788         -- User opted in
15789         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15790     END IF;
15791
15792     IF usr_view_count.value IS NOT NULL THEN
15793         view_count := oils_json_to_text(usr_view_count.value)::INT;
15794     ELSE
15795         view_count := 1000;
15796     END IF;
15797
15798     -- show some fulfilled/canceled holds
15799     FOR h IN
15800         SELECT  *
15801           FROM  action.hold_request
15802           WHERE usr = usr_id
15803                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
15804                 AND request_time > NOW() - view_age
15805           ORDER BY request_time DESC
15806           LIMIT view_count
15807     LOOP
15808         RETURN NEXT h;
15809     END LOOP;
15810
15811     RETURN;
15812 END;
15813 $func$ LANGUAGE PLPGSQL;
15814
15815 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
15816
15817 DROP TABLE IF EXISTS serial.index_summary CASCADE;
15818
15819 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
15820
15821 DROP TABLE IF EXISTS serial.issuance CASCADE;
15822
15823 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
15824
15825 DROP TABLE IF EXISTS serial.subscription CASCADE;
15826
15827 CREATE TABLE asset.copy_template (
15828         id             SERIAL   PRIMARY KEY,
15829         owning_lib     INT      NOT NULL
15830                                 REFERENCES actor.org_unit (id)
15831                                 DEFERRABLE INITIALLY DEFERRED,
15832         creator        BIGINT   NOT NULL
15833                                 REFERENCES actor.usr (id)
15834                                 DEFERRABLE INITIALLY DEFERRED,
15835         editor         BIGINT   NOT NULL
15836                                 REFERENCES actor.usr (id)
15837                                 DEFERRABLE INITIALLY DEFERRED,
15838         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15839         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15840         name           TEXT     NOT NULL,
15841         -- columns above this point are attributes of the template itself
15842         -- columns after this point are attributes of the copy this template modifies/creates
15843         circ_lib       INT      REFERENCES actor.org_unit (id)
15844                                 DEFERRABLE INITIALLY DEFERRED,
15845         status         INT      REFERENCES config.copy_status (id)
15846                                 DEFERRABLE INITIALLY DEFERRED,
15847         location       INT      REFERENCES asset.copy_location (id)
15848                                 DEFERRABLE INITIALLY DEFERRED,
15849         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
15850                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
15851         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
15852                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
15853         age_protect    INT,
15854         circulate      BOOL,
15855         deposit        BOOL,
15856         ref            BOOL,
15857         holdable       BOOL,
15858         deposit_amount NUMERIC(6,2),
15859         price          NUMERIC(8,2),
15860         circ_modifier  TEXT,
15861         circ_as_type   TEXT,
15862         alert_message  TEXT,
15863         opac_visible   BOOL,
15864         floating       BOOL,
15865         mint_condition BOOL
15866 );
15867
15868 CREATE TABLE serial.subscription (
15869         id                     SERIAL       PRIMARY KEY,
15870         owning_lib             INT          NOT NULL DEFAULT 1
15871                                             REFERENCES actor.org_unit (id)
15872                                             ON DELETE SET NULL
15873                                             DEFERRABLE INITIALLY DEFERRED,
15874         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
15875         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
15876         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
15877                                             ON DELETE SET NULL
15878                                             DEFERRABLE INITIALLY DEFERRED,
15879         expected_date_offset   INTERVAL
15880         -- acquisitions/business-side tables link to here
15881 );
15882 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
15883 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
15884
15885 --at least one distribution per org_unit holding issues
15886 CREATE TABLE serial.distribution (
15887         id                    SERIAL  PRIMARY KEY,
15888         record_entry          BIGINT  REFERENCES serial.record_entry (id)
15889                                       ON DELETE SET NULL
15890                                       DEFERRABLE INITIALLY DEFERRED,
15891         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
15892                                           summary_method IS NULL
15893                                           OR summary_method IN ( 'add_to_sre',
15894                                           'merge_with_sre', 'use_sre_only',
15895                                           'use_sdist_only')),
15896         subscription          INT     NOT NULL
15897                                       REFERENCES serial.subscription (id)
15898                                                                   ON DELETE CASCADE
15899                                                                   DEFERRABLE INITIALLY DEFERRED,
15900         holding_lib           INT     NOT NULL
15901                                       REFERENCES actor.org_unit (id)
15902                                                                   DEFERRABLE INITIALLY DEFERRED,
15903         label                 TEXT    NOT NULL,
15904         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
15905                                       DEFERRABLE INITIALLY DEFERRED,
15906         receive_unit_template INT     REFERENCES asset.copy_template (id)
15907                                       DEFERRABLE INITIALLY DEFERRED,
15908         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
15909                                       DEFERRABLE INITIALLY DEFERRED,
15910         bind_unit_template    INT     REFERENCES asset.copy_template (id)
15911                                       DEFERRABLE INITIALLY DEFERRED,
15912         unit_label_prefix     TEXT,
15913         unit_label_suffix     TEXT
15914 );
15915 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
15916 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
15917
15918 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
15919
15920 CREATE TABLE serial.stream (
15921         id              SERIAL  PRIMARY KEY,
15922         distribution    INT     NOT NULL
15923                                 REFERENCES serial.distribution (id)
15924                                 ON DELETE CASCADE
15925                                 DEFERRABLE INITIALLY DEFERRED,
15926         routing_label   TEXT
15927 );
15928 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
15929
15930 CREATE UNIQUE INDEX label_once_per_dist
15931         ON serial.stream (distribution, routing_label)
15932         WHERE routing_label IS NOT NULL;
15933
15934 CREATE TABLE serial.routing_list_user (
15935         id             SERIAL       PRIMARY KEY,
15936         stream         INT          NOT NULL
15937                                     REFERENCES serial.stream
15938                                     ON DELETE CASCADE
15939                                     DEFERRABLE INITIALLY DEFERRED,
15940         pos            INT          NOT NULL DEFAULT 1,
15941         reader         INT          REFERENCES actor.usr
15942                                     ON DELETE CASCADE
15943                                     DEFERRABLE INITIALLY DEFERRED,
15944         department     TEXT,
15945         note           TEXT,
15946         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
15947         CONSTRAINT reader_or_dept CHECK
15948         (
15949             -- Recipient is a person or a department, but not both
15950                 (reader IS NOT NULL AND department IS NULL) OR
15951                 (reader IS NULL AND department IS NOT NULL)
15952         )
15953 );
15954 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
15955 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
15956
15957 CREATE TABLE serial.caption_and_pattern (
15958         id           SERIAL       PRIMARY KEY,
15959         subscription INT          NOT NULL REFERENCES serial.subscription (id)
15960                                   ON DELETE CASCADE
15961                                   DEFERRABLE INITIALLY DEFERRED,
15962         type         TEXT         NOT NULL
15963                                   CONSTRAINT cap_type CHECK ( type in
15964                                   ( 'basic', 'supplement', 'index' )),
15965         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
15966         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
15967         end_date     TIMESTAMP WITH TIME ZONE,
15968         active       BOOL         NOT NULL DEFAULT FALSE,
15969         pattern_code TEXT         NOT NULL,       -- must contain JSON
15970         enum_1       TEXT,
15971         enum_2       TEXT,
15972         enum_3       TEXT,
15973         enum_4       TEXT,
15974         enum_5       TEXT,
15975         enum_6       TEXT,
15976         chron_1      TEXT,
15977         chron_2      TEXT,
15978         chron_3      TEXT,
15979         chron_4      TEXT,
15980         chron_5      TEXT
15981 );
15982 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
15983
15984 CREATE TABLE serial.issuance (
15985         id              SERIAL    PRIMARY KEY,
15986         creator         INT       NOT NULL
15987                                   REFERENCES actor.usr (id)
15988                                                           DEFERRABLE INITIALLY DEFERRED,
15989         editor          INT       NOT NULL
15990                                   REFERENCES actor.usr (id)
15991                                   DEFERRABLE INITIALLY DEFERRED,
15992         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15993         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15994         subscription    INT       NOT NULL
15995                                   REFERENCES serial.subscription (id)
15996                                   ON DELETE CASCADE
15997                                   DEFERRABLE INITIALLY DEFERRED,
15998         label           TEXT,
15999         date_published  TIMESTAMP WITH TIME ZONE,
16000         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
16001                               DEFERRABLE INITIALLY DEFERRED,
16002         holding_code    TEXT,
16003         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
16004                                   (
16005                                       holding_type IS NULL
16006                                       OR holding_type IN ('basic','supplement','index')
16007                                   ),
16008         holding_link_id INT
16009         -- TODO: add columns for separate enumeration/chronology values
16010 );
16011 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
16012 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
16013 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
16014
16015 CREATE TABLE serial.unit (
16016         label           TEXT,
16017         label_sort_key  TEXT,
16018         contents        TEXT    NOT NULL
16019 ) INHERITS (asset.copy);
16020 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
16021 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
16022 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
16023 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
16024 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
16025
16026 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
16027
16028 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
16029
16030 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16031
16032 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16033
16034 CREATE TABLE serial.item (
16035         id              SERIAL  PRIMARY KEY,
16036         creator         INT     NOT NULL
16037                                 REFERENCES actor.usr (id)
16038                                 DEFERRABLE INITIALLY DEFERRED,
16039         editor          INT     NOT NULL
16040                                 REFERENCES actor.usr (id)
16041                                 DEFERRABLE INITIALLY DEFERRED,
16042         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16043         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16044         issuance        INT     NOT NULL
16045                                 REFERENCES serial.issuance (id)
16046                                 ON DELETE CASCADE
16047                                 DEFERRABLE INITIALLY DEFERRED,
16048         stream          INT     NOT NULL
16049                                 REFERENCES serial.stream (id)
16050                                 ON DELETE CASCADE
16051                                 DEFERRABLE INITIALLY DEFERRED,
16052         unit            INT     REFERENCES serial.unit (id)
16053                                 ON DELETE SET NULL
16054                                 DEFERRABLE INITIALLY DEFERRED,
16055         uri             INT     REFERENCES asset.uri (id)
16056                                 ON DELETE SET NULL
16057                                 DEFERRABLE INITIALLY DEFERRED,
16058         date_expected   TIMESTAMP WITH TIME ZONE,
16059         date_received   TIMESTAMP WITH TIME ZONE,
16060         status          TEXT    CONSTRAINT valid_status CHECK (
16061                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
16062                                'Expected', 'Not Held', 'Not Published', 'Received'))
16063                             DEFAULT 'Expected',
16064         shadowed        BOOL    NOT NULL DEFAULT FALSE
16065 );
16066 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
16067 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
16068 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
16069 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
16070 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
16071 CREATE INDEX serial_item_status_idx ON serial.item (status);
16072
16073 CREATE TABLE serial.item_note (
16074         id          SERIAL  PRIMARY KEY,
16075         item        INT     NOT NULL
16076                             REFERENCES serial.item (id)
16077                             ON DELETE CASCADE
16078                             DEFERRABLE INITIALLY DEFERRED,
16079         creator     INT     NOT NULL
16080                             REFERENCES actor.usr (id)
16081                             DEFERRABLE INITIALLY DEFERRED,
16082         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
16083         pub         BOOL    NOT NULL    DEFAULT FALSE,
16084         title       TEXT    NOT NULL,
16085         value       TEXT    NOT NULL
16086 );
16087 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
16088
16089 CREATE TABLE serial.basic_summary (
16090         id                  SERIAL  PRIMARY KEY,
16091         distribution        INT     NOT NULL
16092                                     REFERENCES serial.distribution (id)
16093                                     ON DELETE CASCADE
16094                                     DEFERRABLE INITIALLY DEFERRED,
16095         generated_coverage  TEXT    NOT NULL,
16096         textual_holdings    TEXT,
16097         show_generated      BOOL    NOT NULL DEFAULT TRUE
16098 );
16099 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
16100
16101 CREATE TABLE serial.supplement_summary (
16102         id                  SERIAL  PRIMARY KEY,
16103         distribution        INT     NOT NULL
16104                                     REFERENCES serial.distribution (id)
16105                                     ON DELETE CASCADE
16106                                     DEFERRABLE INITIALLY DEFERRED,
16107         generated_coverage  TEXT    NOT NULL,
16108         textual_holdings    TEXT,
16109         show_generated      BOOL    NOT NULL DEFAULT TRUE
16110 );
16111 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
16112
16113 CREATE TABLE serial.index_summary (
16114         id                  SERIAL  PRIMARY KEY,
16115         distribution        INT     NOT NULL
16116                                     REFERENCES serial.distribution (id)
16117                                     ON DELETE CASCADE
16118                                     DEFERRABLE INITIALLY DEFERRED,
16119         generated_coverage  TEXT    NOT NULL,
16120         textual_holdings    TEXT,
16121         show_generated      BOOL    NOT NULL DEFAULT TRUE
16122 );
16123 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
16124
16125 -- 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.
16126
16127 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
16128 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16129
16130 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
16131 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;
16132
16133 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
16134 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16135
16136 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
16137 RETURNS INTEGER AS $$
16138 BEGIN
16139         RETURN EXTRACT( EPOCH FROM interval_val );
16140 END;
16141 $$ LANGUAGE plpgsql;
16142
16143 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
16144 RETURNS INTEGER AS $$
16145 BEGIN
16146         RETURN config.interval_to_seconds( interval_string::INTERVAL );
16147 END;
16148 $$ LANGUAGE plpgsql;
16149
16150 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
16151     'temp',
16152     oils_i18n_gettext(
16153         'temp',
16154         'Temporary bucket which gets deleted after use.',
16155         'cbrebt',
16156         'label'
16157     )
16158 );
16159
16160 -- 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.
16161
16162 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
16163 BEGIN
16164
16165     IF xml_is_well_formed(NEW.marc) THEN
16166         RETURN NEW;
16167     ELSE
16168         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16169     END IF;
16170     
16171 END;
16172 $func$ LANGUAGE PLPGSQL;
16173
16174 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();
16175
16176 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();
16177
16178 ALTER TABLE serial.record_entry
16179         ALTER COLUMN marc DROP NOT NULL;
16180
16181 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16182 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16183 <xsl:stylesheet
16184     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16185     xmlns:marc="http://www.loc.gov/MARC21/slim"
16186     version="1.0">
16187 <!--
16188 Copyright (C) 2010  Equinox Software, Inc.
16189 Galen Charlton <gmc@esilibrary.cOM.
16190
16191 This program is free software; you can redistribute it and/or
16192 modify it under the terms of the GNU General Public License
16193 as published by the Free Software Foundation; either version 2
16194 of the License, or (at your option) any later version.
16195
16196 This program is distributed in the hope that it will be useful,
16197 but WITHOUT ANY WARRANTY; without even the implied warranty of
16198 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16199 GNU General Public License for more details.
16200
16201 marc21_expand_880.xsl - stylesheet used during indexing to
16202                         map alternative graphical representations
16203                         of MARC fields stored in 880 fields
16204                         to the corresponding tag name and value.
16205
16206 For example, if a MARC record for a Chinese book has
16207
16208 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16209 880.00 $6 245-01/$1 $a八十三年短篇小說選
16210
16211 this stylesheet will transform it to the equivalent of
16212
16213 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16214 245.00 $6 245-01/$1 $a八十三年短篇小說選
16215
16216 -->
16217     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16218
16219     <xsl:template match="@*|node()">
16220         <xsl:copy>
16221             <xsl:apply-templates select="@*|node()"/>
16222         </xsl:copy>
16223     </xsl:template>
16224
16225     <xsl:template match="//marc:datafield[@tag='880']">
16226         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16227             <marc:datafield>
16228                 <xsl:attribute name="tag">
16229                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16230                 </xsl:attribute>
16231                 <xsl:attribute name="ind1">
16232                     <xsl:value-of select="@ind1" />
16233                 </xsl:attribute>
16234                 <xsl:attribute name="ind2">
16235                     <xsl:value-of select="@ind2" />
16236                 </xsl:attribute>
16237                 <xsl:apply-templates />
16238             </marc:datafield>
16239         </xsl:if>
16240     </xsl:template>
16241     
16242 </xsl:stylesheet>$$);
16243
16244 -- Splitting the ingest trigger up into little bits
16245
16246 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16247     flag INTEGER PRIMARY KEY
16248 ) ON COMMIT DROP;
16249 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16250
16251 -- cause failure if either of the tables we want to drop have rows
16252 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16253 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16254
16255 DROP TABLE IF EXISTS asset.copy_transparency_map;
16256 DROP TABLE IF EXISTS asset.copy_transparency;
16257
16258 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16259
16260 -- We won't necessarily use all of these, but they are here for completeness.
16261 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16262 -- Values are the EDI code value + 1000
16263
16264 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16265 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16266 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16267 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16268 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16269 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16270 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16271 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16272 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16273 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16274 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16275 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16276 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16277 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16278 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16279 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16280 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16281 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16282 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16283 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16284 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16285 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16286 ('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).'),
16287 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16288 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16289 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16290 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16291 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16292 ('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.'),
16293 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16294 ('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.'),
16295 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16296 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16297 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16298 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16299 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16300 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16301 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16302 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16303 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16304 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16305 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16306 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16307 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16308 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16309 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16310 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16311 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16312 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16313 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16314 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16315 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16316 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16317 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16318 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16319 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16320 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16321 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16322 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16323 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16324 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16325 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16326 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16327 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16328 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16329 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16330 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16331 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16332 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16333 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16334 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16335 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16336 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16337 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16338 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16339 ('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).'),
16340 ('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).'),
16341 ('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).'),
16342 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16343 ('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).'),
16344 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16345 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16346 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16347 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16348 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16349 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16350 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16351 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16352 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16353 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16354 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16355 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16356 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16357 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16358 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16359 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16360 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16361 ('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.'),
16362 ('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.'),
16363 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16364 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16365 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16366 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16367 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16368 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16369 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16370 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16371 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16372 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16373 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16374 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16375 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16376 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16377 ('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.'),
16378 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16379 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16380
16381 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16382     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16383  
16384 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16385  
16386 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16387         'Remove Parenthesized Substring',
16388         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16389         'remove_paren_substring',
16390         0
16391 );
16392
16393 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16394         'Trim Surrounding Space',
16395         'Trim leading and trailing spaces from extracted text.',
16396         'btrim',
16397         0
16398 );
16399
16400 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16401     SELECT  m.id,
16402             i.id,
16403             -2
16404       FROM  config.metabib_field m,
16405             config.index_normalizer i
16406       WHERE i.func IN ('remove_paren_substring')
16407             AND m.id IN (26);
16408
16409 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16410     SELECT  m.id,
16411             i.id,
16412             -1
16413       FROM  config.metabib_field m,
16414             config.index_normalizer i
16415       WHERE i.func IN ('btrim')
16416             AND m.id IN (26);
16417
16418 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16419 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16420 DECLARE
16421     dyn_profile     vandelay.compile_profile%ROWTYPE;
16422     replace_rule    TEXT;
16423     tmp_marc        TEXT;
16424     trgt_marc        TEXT;
16425     tmpl_marc        TEXT;
16426     match_count     INT;
16427 BEGIN
16428
16429     IF target_marc IS NULL OR template_marc IS NULL THEN
16430         -- RAISE NOTICE 'no marc for target or template record';
16431         RETURN NULL;
16432     END IF;
16433
16434     dyn_profile := vandelay.compile_profile( template_marc );
16435
16436     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16437         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16438         RETURN NULL;
16439     END IF;
16440
16441     IF dyn_profile.replace_rule <> '' THEN
16442         trgt_marc = target_marc;
16443         tmpl_marc = template_marc;
16444         replace_rule = dyn_profile.replace_rule;
16445     ELSE
16446         tmp_marc = target_marc;
16447         trgt_marc = template_marc;
16448         tmpl_marc = tmp_marc;
16449         replace_rule = dyn_profile.preserve_rule;
16450     END IF;
16451
16452     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16453
16454 END;
16455 $$ LANGUAGE PLPGSQL;
16456
16457 -- Function to generate an ephemeral overlay template from an authority record
16458 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16459
16460     use MARC::Record;
16461     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16462
16463     my $xml = shift;
16464     my $r = MARC::Record->new_from_xml( $xml );
16465
16466     return undef unless ($r);
16467
16468     my $id = shift() || $r->subfield( '901' => 'c' );
16469     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16470     return undef unless ($id); # We need an ID!
16471
16472     my $tmpl = MARC::Record->new();
16473
16474     my @rule_fields;
16475     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16476
16477         my $tag = $field->tag;
16478         my $i1 = $field->indicator(1);
16479         my $i2 = $field->indicator(2);
16480         my $sf = join '', map { $_->[0] } $field->subfields;
16481         my @data = map { @$_ } $field->subfields;
16482
16483         my @replace_them;
16484
16485         # Map the authority field to bib fields it can control.
16486         if ($tag >= 100 and $tag <= 111) {       # names
16487             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16488         } elsif ($tag eq '130') {                # uniform title
16489             @replace_them = qw/130 240 440 730 830/;
16490         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16491             @replace_them = ($tag + 500);
16492         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16493             @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/;
16494         } else {
16495             next;
16496         }
16497
16498         # Dummy up the bib-side data
16499         $tmpl->append_fields(
16500             map {
16501                 MARC::Field->new( $_, $i1, $i2, @data )
16502             } @replace_them
16503         );
16504
16505         # Construct some 'replace' rules
16506         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16507     }
16508
16509     # Insert the replace rules into the template
16510     $tmpl->append_fields(
16511         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16512     );
16513
16514     $xml = $tmpl->as_xml_record;
16515     $xml =~ s/^<\?.+?\?>$//mo;
16516     $xml =~ s/\n//sgo;
16517     $xml =~ s/>\s+</></sgo;
16518
16519     return $xml;
16520
16521 $func$ LANGUAGE PLPERLU;
16522
16523 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16524     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16525 $func$ LANGUAGE SQL;
16526
16527 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16528     SELECT authority.generate_overlay_template( $1, NULL );
16529 $func$ LANGUAGE SQL;
16530
16531 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16532 DELETE FROM config.metabib_field WHERE id = 26;
16533
16534 -- Making this a global_flag (UI accessible) instead of an internal_flag
16535 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16536     VALUES (
16537         'ingest.disable_authority_linking',
16538         oils_i18n_gettext(
16539             'ingest.disable_authority_linking',
16540             'Authority Automation: Disable bib-authority link tracking',
16541             'cgf', 
16542             'label'
16543         )
16544     );
16545 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16546 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16547
16548 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16549     VALUES (
16550         'ingest.disable_authority_auto_update',
16551         oils_i18n_gettext(
16552             'ingest.disable_authority_auto_update',
16553             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16554             'cgf', 
16555             'label'
16556         )
16557     );
16558
16559 -- Enable automated ingest of authority records; just insert the row into
16560 -- authority.record_entry and authority.full_rec will automatically be populated
16561
16562 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16563     UPDATE  biblio.record_entry
16564       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16565       WHERE id = $2;
16566     SELECT $1;
16567 $func$ LANGUAGE SQL;
16568
16569 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16570     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16571 $func$ LANGUAGE SQL;
16572
16573 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16574
16575 use MARC::Record;
16576 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16577
16578 my $xml = shift;
16579 my $r = MARC::Record->new_from_xml( $xml );
16580
16581 return_next( { tag => 'LDR', value => $r->leader } );
16582
16583 for my $f ( $r->fields ) {
16584     if ($f->is_control_field) {
16585         return_next({ tag => $f->tag, value => $f->data });
16586     } else {
16587         for my $s ($f->subfields) {
16588             return_next({
16589                 tag      => $f->tag,
16590                 ind1     => $f->indicator(1),
16591                 ind2     => $f->indicator(2),
16592                 subfield => $s->[0],
16593                 value    => $s->[1]
16594             });
16595
16596         }
16597     }
16598 }
16599
16600 return undef;
16601
16602 $func$ LANGUAGE PLPERLU;
16603
16604 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16605 DECLARE
16606     auth    authority.record_entry%ROWTYPE;
16607     output    authority.full_rec%ROWTYPE;
16608     field    RECORD;
16609 BEGIN
16610     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16611
16612     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16613         output.record := rid;
16614         output.ind1 := field.ind1;
16615         output.ind2 := field.ind2;
16616         output.tag := field.tag;
16617         output.subfield := field.subfield;
16618         IF field.subfield IS NOT NULL THEN
16619             output.value := naco_normalize(field.value, field.subfield);
16620         ELSE
16621             output.value := field.value;
16622         END IF;
16623
16624         CONTINUE WHEN output.value IS NULL;
16625
16626         RETURN NEXT output;
16627     END LOOP;
16628 END;
16629 $func$ LANGUAGE PLPGSQL;
16630
16631 -- authority.rec_descriptor appears to be unused currently
16632 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16633 BEGIN
16634     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16635 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16636 --        SELECT  auth_id, ;
16637
16638     RETURN;
16639 END;
16640 $func$ LANGUAGE PLPGSQL;
16641
16642 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16643 BEGIN
16644     DELETE FROM authority.full_rec WHERE record = auth_id;
16645     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16646         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16647
16648     RETURN;
16649 END;
16650 $func$ LANGUAGE PLPGSQL;
16651
16652 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16653 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16654 BEGIN
16655
16656     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16657         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16658         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
16659           -- Should remove matching $0 from controlled fields at the same time?
16660         RETURN NEW; -- and we're done
16661     END IF;
16662
16663     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16664         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16665
16666         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16667             RETURN NEW;
16668         END IF;
16669     END IF;
16670
16671     -- Flatten and insert the afr data
16672     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16673     IF NOT FOUND THEN
16674         PERFORM authority.reingest_authority_full_rec(NEW.id);
16675 -- authority.rec_descriptor is not currently used
16676 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16677 --        IF NOT FOUND THEN
16678 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16679 --        END IF;
16680     END IF;
16681
16682     RETURN NEW;
16683 END;
16684 $func$ LANGUAGE PLPGSQL;
16685
16686 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 ();
16687
16688 -- Some records manage to get XML namespace declarations into each element,
16689 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16690 -- This broke the old maintain_901(), so we'll make the regex more robust
16691
16692 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16693 BEGIN
16694     -- Remove any existing 901 fields before we insert the authoritative one
16695     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16696     IF TG_TABLE_SCHEMA = 'biblio' THEN
16697         NEW.marc := REGEXP_REPLACE(
16698             NEW.marc,
16699             E'(</(?:[^:]*?:)?record>)',
16700             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16701                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16702                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16703                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16704                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16705                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16706                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16707              E'</datafield>\\1'
16708         );
16709     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16710         NEW.marc := REGEXP_REPLACE(
16711             NEW.marc,
16712             E'(</(?:[^:]*?:)?record>)',
16713             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16714                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16715                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16716              E'</datafield>\\1'
16717         );
16718     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16719         NEW.marc := REGEXP_REPLACE(
16720             NEW.marc,
16721             E'(</(?:[^:]*?:)?record>)',
16722             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16723                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16724                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16725                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16726                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16727              E'</datafield>\\1'
16728         );
16729     ELSE
16730         NEW.marc := REGEXP_REPLACE(
16731             NEW.marc,
16732             E'(</(?:[^:]*?:)?record>)',
16733             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16734                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16735                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16736              E'</datafield>\\1'
16737         );
16738     END IF;
16739
16740     RETURN NEW;
16741 END;
16742 $func$ LANGUAGE PLPGSQL;
16743
16744 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16745 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16746 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16747  
16748 -- In booking, elbow room defines:
16749 --  a) how far in the future you must make a reservation on a given item if
16750 --      that item will have to transit somewhere to fulfill the reservation.
16751 --  b) how soon a reservation must be starting for the reserved item to
16752 --      be op-captured by the checkin interface.
16753
16754 -- We don't want to clobber any default_elbow room at any level:
16755
16756 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16757 DECLARE
16758     existing    actor.org_unit_setting%ROWTYPE;
16759 BEGIN
16760     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16761     IF NOT FOUND THEN
16762         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16763             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16764             'circ.booking_reservation.default_elbow_room',
16765             '"1 day"'
16766         );
16767         RETURN 1;
16768     END IF;
16769     RETURN 0;
16770 END;
16771 $$ LANGUAGE plpgsql;
16772
16773 SELECT pg_temp.default_elbow();
16774
16775 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16776
16777 -- returns the distinct set of target copy IDs from a user's visible circulation history
16778 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16779     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16780 $$ LANGUAGE SQL;
16781
16782 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
16783 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
16784 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
16785 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
16786
16787 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
16788
16789 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
16790 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
16791
16792 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16793     VALUES (
16794         'cat.maintain_control_numbers',
16795         oils_i18n_gettext(
16796             'cat.maintain_control_numbers',
16797             'Cat: Maintain 001/003/035 according to the MARC21 specification',
16798             'cgf', 
16799             'label'
16800         )
16801     );
16802
16803 INSERT INTO config.global_flag (name, label, enabled)
16804     VALUES (
16805         'circ.holds.empty_issuance_ok',
16806         oils_i18n_gettext(
16807             'circ.holds.empty_issuance_ok',
16808             'Holds: Allow holds on empty issuances',
16809             'cgf',
16810             'label'
16811         ),
16812         TRUE
16813     );
16814
16815 INSERT INTO config.global_flag (name, label, enabled)
16816     VALUES (
16817         'circ.holds.usr_not_requestor',
16818         oils_i18n_gettext(
16819             'circ.holds.usr_not_requestor',
16820             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
16821             'cgf',
16822             'label'
16823         ),
16824         TRUE
16825     );
16826
16827 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
16828 use strict;
16829 use MARC::Record;
16830 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16831 use Encode;
16832 use Unicode::Normalize;
16833
16834 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
16835 my $schema = $_TD->{table_schema};
16836 my $rec_id = $_TD->{new}{id};
16837
16838 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
16839 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
16840 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
16841     return;
16842 }
16843
16844 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
16845 my $ou_cni = 'EVRGRN';
16846
16847 my $owner;
16848 if ($schema eq 'serial') {
16849     $owner = $_TD->{new}{owning_lib};
16850 } else {
16851     # are.owner and bre.owner can be null, so fall back to the consortial setting
16852     $owner = $_TD->{new}{owner} || 1;
16853 }
16854
16855 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
16856 if ($ous_rv->{processed}) {
16857     $ou_cni = $ous_rv->{rows}[0]->{value};
16858     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
16859 } else {
16860     # Fall back to the shortname of the OU if there was no OU setting
16861     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
16862     if ($ous_rv->{processed}) {
16863         $ou_cni = $ous_rv->{rows}[0]->{shortname};
16864     }
16865 }
16866
16867 my ($create, $munge) = (0, 0);
16868 my ($orig_001, $orig_003) = ('', '');
16869
16870 # Incoming MARC records may have multiple 001s or 003s, despite the spec
16871 my @control_ids = $record->field('003');
16872 my @scns = $record->field('035');
16873
16874 foreach my $id_field ('001', '003') {
16875     my $spec_value;
16876     my @controls = $record->field($id_field);
16877
16878     if ($id_field eq '001') {
16879         $spec_value = $rec_id;
16880     } else {
16881         $spec_value = $ou_cni;
16882     }
16883
16884     # Create the 001/003 if none exist
16885     if (scalar(@controls) == 0) {
16886         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
16887         $create = 1;
16888     } elsif (scalar(@controls) > 1) {
16889         # Do we already have the right 001/003 value in the existing set?
16890         unless (grep $_->data() eq $spec_value, @controls) {
16891             $munge = 1;
16892         }
16893
16894         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
16895         foreach my $control (@controls) {
16896             unless ($control->data() eq $spec_value) {
16897                 $record->delete_field($control);
16898             }
16899         }
16900     } else {
16901         # Only one field; check to see if we need to munge it
16902         unless (grep $_->data() eq $spec_value, @controls) {
16903             $munge = 1;
16904         }
16905     }
16906 }
16907
16908 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
16909 if ($munge) {
16910     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
16911
16912     # Do not create duplicate 035 fields
16913     unless (grep $_->subfield('a') eq $scn, @scns) {
16914         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
16915     }
16916 }
16917
16918 # Set the 001/003 and update the MARC
16919 if ($create or $munge) {
16920     $record->field('001')->data($rec_id);
16921     $record->field('003')->data($ou_cni);
16922
16923     my $xml = $record->as_xml_record();
16924     $xml =~ s/\n//sgo;
16925     $xml =~ s/^<\?xml.+\?\s*>//go;
16926     $xml =~ s/>\s+</></go;
16927     $xml =~ s/\p{Cc}//go;
16928
16929     # Embed a version of OpenILS::Application::AppUtils->entityize()
16930     # to avoid having to set PERL5LIB for PostgreSQL as well
16931
16932     # If we are going to convert non-ASCII characters to XML entities,
16933     # we had better be dealing with a UTF8 string to begin with
16934     $xml = decode_utf8($xml);
16935
16936     $xml = NFC($xml);
16937
16938     # Convert raw ampersands to entities
16939     $xml =~ s/&(?!\S+;)/&amp;/gso;
16940
16941     # Convert Unicode characters to entities
16942     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
16943
16944     $xml =~ s/[\x00-\x1f]//go;
16945     $_TD->{new}{marc} = $xml;
16946
16947     return "MODIFY";
16948 }
16949
16950 return;
16951 $func$ LANGUAGE PLPERLU;
16952
16953 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16954 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16955 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16956
16957 INSERT INTO metabib.facet_entry (source, field, value)
16958     SELECT source, field, value FROM (
16959         SELECT * FROM metabib.author_field_entry
16960             UNION ALL
16961         SELECT * FROM metabib.keyword_field_entry
16962             UNION ALL
16963         SELECT * FROM metabib.identifier_field_entry
16964             UNION ALL
16965         SELECT * FROM metabib.title_field_entry
16966             UNION ALL
16967         SELECT * FROM metabib.subject_field_entry
16968             UNION ALL
16969         SELECT * FROM metabib.series_field_entry
16970         )x
16971     WHERE x.index_vector = '';
16972         
16973 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
16974 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
16975 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
16976 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
16977 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
16978 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
16979
16980 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
16981 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
16982 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
16983
16984 -- copy OPAC visibility materialized view
16985 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
16986
16987     TRUNCATE TABLE asset.opac_visible_copies;
16988
16989     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16990     SELECT  cp.id, cp.circ_lib, cn.record
16991     FROM  asset.copy cp
16992         JOIN asset.call_number cn ON (cn.id = cp.call_number)
16993         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
16994         JOIN asset.copy_location cl ON (cp.location = cl.id)
16995         JOIN config.copy_status cs ON (cp.status = cs.id)
16996         JOIN biblio.record_entry b ON (cn.record = b.id)
16997     WHERE NOT cp.deleted
16998         AND NOT cn.deleted
16999         AND NOT b.deleted
17000         AND cs.opac_visible
17001         AND cl.opac_visible
17002         AND cp.opac_visible
17003         AND a.opac_visible;
17004
17005 $$ LANGUAGE SQL;
17006 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
17007 Rebuild the copy OPAC visibility cache.  Useful during migrations.
17008 $$;
17009
17010 -- and actually populate the table
17011 SELECT asset.refresh_opac_visible_copies_mat_view();
17012
17013 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
17014 DECLARE
17015     add_query       TEXT;
17016     remove_query    TEXT;
17017     do_add          BOOLEAN := false;
17018     do_remove       BOOLEAN := false;
17019 BEGIN
17020     add_query := $$
17021             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17022                 SELECT  cp.id, cp.circ_lib, cn.record
17023                   FROM  asset.copy cp
17024                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17025                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17026                         JOIN asset.copy_location cl ON (cp.location = cl.id)
17027                         JOIN config.copy_status cs ON (cp.status = cs.id)
17028                         JOIN biblio.record_entry b ON (cn.record = b.id)
17029                   WHERE NOT cp.deleted
17030                         AND NOT cn.deleted
17031                         AND NOT b.deleted
17032                         AND cs.opac_visible
17033                         AND cl.opac_visible
17034                         AND cp.opac_visible
17035                         AND a.opac_visible
17036     $$;
17037  
17038     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
17039
17040     IF TG_OP = 'INSERT' THEN
17041
17042         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17043             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17044             EXECUTE add_query;
17045         END IF;
17046
17047         RETURN NEW;
17048
17049     END IF;
17050
17051     -- handle items first, since with circulation activity
17052     -- their statuses change frequently
17053     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17054
17055         IF OLD.location    <> NEW.location OR
17056            OLD.call_number <> NEW.call_number OR
17057            OLD.status      <> NEW.status OR
17058            OLD.circ_lib    <> NEW.circ_lib THEN
17059             -- any of these could change visibility, but
17060             -- we'll save some queries and not try to calculate
17061             -- the change directly
17062             do_remove := true;
17063             do_add := true;
17064         ELSE
17065
17066             IF OLD.deleted <> NEW.deleted THEN
17067                 IF NEW.deleted THEN
17068                     do_remove := true;
17069                 ELSE
17070                     do_add := true;
17071                 END IF;
17072             END IF;
17073
17074             IF OLD.opac_visible <> NEW.opac_visible THEN
17075                 IF OLD.opac_visible THEN
17076                     do_remove := true;
17077                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
17078                                         -- is also marked opac_visible
17079                     do_add := true;
17080                 END IF;
17081             END IF;
17082
17083         END IF;
17084
17085         IF do_remove THEN
17086             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
17087         END IF;
17088         IF do_add THEN
17089             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17090             EXECUTE add_query;
17091         END IF;
17092
17093         RETURN NEW;
17094
17095     END IF;
17096
17097     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
17098  
17099         IF OLD.deleted AND NEW.deleted THEN -- do nothing
17100
17101             RETURN NEW;
17102  
17103         ELSIF NEW.deleted THEN -- remove rows
17104  
17105             IF TG_TABLE_NAME = 'call_number' THEN
17106                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
17107             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17108                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
17109             END IF;
17110  
17111             RETURN NEW;
17112  
17113         ELSIF OLD.deleted THEN -- add rows
17114  
17115             IF TG_TABLE_NAME IN ('copy','unit') THEN
17116                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17117             ELSIF TG_TABLE_NAME = 'call_number' THEN
17118                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17119             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17120                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
17121             END IF;
17122  
17123             EXECUTE add_query;
17124             RETURN NEW;
17125  
17126         END IF;
17127  
17128     END IF;
17129
17130     IF TG_TABLE_NAME = 'call_number' THEN
17131
17132         IF OLD.record <> NEW.record THEN
17133             -- call number is linked to different bib
17134             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
17135             EXECUTE remove_query;
17136             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17137             EXECUTE add_query;
17138         END IF;
17139
17140         RETURN NEW;
17141
17142     END IF;
17143
17144     IF TG_TABLE_NAME IN ('record_entry') THEN
17145         RETURN NEW; -- don't have 'opac_visible'
17146     END IF;
17147
17148     -- actor.org_unit, asset.copy_location, asset.copy_status
17149     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
17150
17151         RETURN NEW;
17152
17153     ELSIF NEW.opac_visible THEN -- add rows
17154
17155         IF TG_TABLE_NAME = 'org_unit' THEN
17156             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
17157         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17158             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
17159         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17160             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
17161         END IF;
17162  
17163         EXECUTE add_query;
17164  
17165     ELSE -- delete rows
17166
17167         IF TG_TABLE_NAME = 'org_unit' THEN
17168             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17169         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17170             remove_query := remove_query || 'location = ' || NEW.id || ');';
17171         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17172             remove_query := remove_query || 'status = ' || NEW.id || ');';
17173         END IF;
17174  
17175         EXECUTE remove_query;
17176  
17177     END IF;
17178  
17179     RETURN NEW;
17180 END;
17181 $func$ LANGUAGE PLPGSQL;
17182 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17183 Trigger function to update the copy OPAC visiblity cache.
17184 $$;
17185 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();
17186 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17187 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();
17188 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();
17189 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17190 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();
17191 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();
17192
17193 -- must create this rule explicitly; it is not inherited from asset.copy
17194 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;
17195
17196 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);
17197
17198 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17199 DECLARE
17200     moved_objects INT := 0;
17201     bib_id        INT := 0;
17202     bib_rec       biblio.record_entry%ROWTYPE;
17203     auth_link     authority.bib_linking%ROWTYPE;
17204 BEGIN
17205
17206     -- 1. Make source_record MARC a copy of the target_record to get auto-sync in linked bib records
17207     UPDATE authority.record_entry
17208       SET marc = (
17209         SELECT marc
17210           FROM authority.record_entry
17211           WHERE id = target_record
17212       )
17213       WHERE id = source_record;
17214
17215     -- 2. Update all bib records with the ID from target_record in their $0
17216     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre 
17217       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17218       WHERE abl.authority = target_record LOOP
17219
17220         UPDATE biblio.record_entry
17221           SET marc = REGEXP_REPLACE(marc, 
17222             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17223             E'\\1' || target_record || '<', 'g')
17224           WHERE id = bib_rec.id;
17225
17226           moved_objects := moved_objects + 1;
17227     END LOOP;
17228
17229     -- 3. "Delete" source_record
17230     DELETE FROM authority.record_entry
17231       WHERE id = source_record;
17232
17233     RETURN moved_objects;
17234 END;
17235 $func$ LANGUAGE plpgsql;
17236
17237 -- serial.record_entry already had an owner column spelled "owning_lib"
17238 -- Adjust the table and affected functions accordingly
17239
17240 ALTER TABLE serial.record_entry DROP COLUMN owner;
17241
17242 CREATE TABLE actor.usr_saved_search (
17243     id              SERIAL          PRIMARY KEY,
17244         owner           INT             NOT NULL REFERENCES actor.usr (id)
17245                                         ON DELETE CASCADE
17246                                         DEFERRABLE INITIALLY DEFERRED,
17247         name            TEXT            NOT NULL,
17248         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17249         query_text      TEXT            NOT NULL,
17250         query_type      TEXT            NOT NULL
17251                                         CONSTRAINT valid_query_text CHECK (
17252                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17253                                         -- we may add other types someday
17254         target          TEXT            NOT NULL
17255                                         CONSTRAINT valid_target CHECK (
17256                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17257         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17258 );
17259
17260 -- Apply Dan Wells' changes to the serial schema, from the
17261 -- seials-integration branch
17262
17263 CREATE TABLE serial.subscription_note (
17264         id           SERIAL PRIMARY KEY,
17265         subscription INT    NOT NULL
17266                             REFERENCES serial.subscription (id)
17267                             ON DELETE CASCADE
17268                             DEFERRABLE INITIALLY DEFERRED,
17269         creator      INT    NOT NULL
17270                             REFERENCES actor.usr (id)
17271                             DEFERRABLE INITIALLY DEFERRED,
17272         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17273         pub          BOOL   NOT NULL DEFAULT FALSE,
17274         title        TEXT   NOT NULL,
17275         value        TEXT   NOT NULL
17276 );
17277 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17278
17279 CREATE TABLE serial.distribution_note (
17280         id           SERIAL PRIMARY KEY,
17281         distribution INT    NOT NULL
17282                             REFERENCES serial.distribution (id)
17283                             ON DELETE CASCADE
17284                             DEFERRABLE INITIALLY DEFERRED,
17285         creator      INT    NOT NULL
17286                             REFERENCES actor.usr (id)
17287                             DEFERRABLE INITIALLY DEFERRED,
17288         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17289         pub          BOOL   NOT NULL DEFAULT FALSE,
17290         title        TEXT   NOT NULL,
17291         value        TEXT   NOT NULL
17292 );
17293 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17294
17295 ------- Begin surgery on serial.unit
17296
17297 ALTER TABLE serial.unit
17298         DROP COLUMN label;
17299
17300 ALTER TABLE serial.unit
17301         RENAME COLUMN label_sort_key TO sort_key;
17302
17303 ALTER TABLE serial.unit
17304         RENAME COLUMN contents TO detailed_contents;
17305
17306 ALTER TABLE serial.unit
17307         ADD COLUMN summary_contents TEXT;
17308
17309 UPDATE serial.unit
17310 SET summary_contents = detailed_contents;
17311
17312 ALTER TABLE serial.unit
17313         ALTER column summary_contents SET NOT NULL;
17314
17315 ------- End surgery on serial.unit
17316
17317 -- 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' );
17318
17319 -- Now rebuild the constraints dropped via cascade.
17320 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17321 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17322 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17323
17324 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17325
17326 DELETE FROM config.metabib_field_index_norm_map
17327     WHERE norm IN (
17328         SELECT id 
17329             FROM config.index_normalizer
17330             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17331     )
17332     AND field = 18
17333 ;
17334
17335 -- We won't necessarily use all of these, but they are here for completeness.
17336 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17337 -- Values are the EDI code value + 1200
17338
17339 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17340 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17341 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17342 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17343 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17344 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17345 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17346 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17347 (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.'),
17348 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17349 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17350 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17351 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17352 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17353 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17354 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17355 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17356 (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.'),
17357 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17358 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17359 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17360 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17361 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17362 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17363 (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.'),
17364 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17365 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17366 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17367 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17368 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17369 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17370 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17371 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17372 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17373 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17374 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17375 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17376 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17377 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17378 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17379 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17380 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17381 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17382 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17383 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17384 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17385 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17386 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17387 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17388 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17389 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17390 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17391 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17392 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17393 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17394 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17395 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17396 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17397 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17398 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17399 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17400 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17401 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17402 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17403 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17404 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17405 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17406 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17407 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17408 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17409 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17410 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17411 (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.'),
17412 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17413 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17414 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17415 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17416 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17417 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17418 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17419 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17420 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17421 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17422 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17423 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17424 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17425 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17426 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17427 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17428 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17429 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17430 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17431 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17432 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17433 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17434 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17435 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17436 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17437 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17438 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17439 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17440 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17441 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17442 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17443 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17444 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17445 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17446 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17447 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17448 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17449 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17450 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17451 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17452 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17453 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17454 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17455 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17456 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17457 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17458 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17459 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17460 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17461 (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.'),
17462 (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.'),
17463 (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.'),
17464 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17465 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17466 (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.'),
17467 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17468 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17469 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17470 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17471 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17472 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17473 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17474 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17475 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17476 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17477 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17478 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17479 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17480 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17481 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17482 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17483 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17484 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17485 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17486 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17487 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17488 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17489 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17490 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17491 (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.'),
17492 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17493 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17494 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17495 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17496 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17497 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17498 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17499 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17500 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17501 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17502 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17503 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17504 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17505 (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.'),
17506 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17507 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17508 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17509 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17510 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17511 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17512 (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.'),
17513 (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.'),
17514 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17515 (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.'),
17516 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17517 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17518 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17519 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17520 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17521 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17522 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17523 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17524 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17525 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17526 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17527 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17528 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17529 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17530 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17531 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17532 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17533 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17534 (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.'),
17535 (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.'),
17536 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17537 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17538 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17539 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17540 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17541 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17542 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17543 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17544 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17545 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17546 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17547 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17548 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17549 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17550 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17551 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17552 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17553 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17554 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17555 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17556 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17557 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17558 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17559 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17560 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17561 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17562 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17563 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17564 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17565 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17566 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17567 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17568 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17569 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17570 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17571 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17572 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17573 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17574 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17575 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17576 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17577 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17578 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17579 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17580 (1, 't', 1442, 'Number of months', 'The number of months.'),
17581 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17582 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17583 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17584 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17585 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17586 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17587 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17588 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17589 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17590 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17591 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17592 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17593 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17594 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17595 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17596 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17597 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17598 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17599 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17600 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17601 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17602 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17603 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17604 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17605 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17606 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17607 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17608 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17609 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17610 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17611 (1, 't', 1473, 'Agents', 'The number of agents.'),
17612 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17613 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17614 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17615 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17616 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17617 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17618 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17619 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17620 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17621 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17622 (1, 't', 1484, 'Departments', 'The number of departments.'),
17623 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17624 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17625 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17626 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17627 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17628 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17629 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17630 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17631 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17632 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17633 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17634 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17635 (1, 't', 1497, 'Executives', 'The number of executives.'),
17636 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17637 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17638 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17639 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17640 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17641 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17642 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17643 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17644 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17645 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17646 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17647 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17648 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17649 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17650 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17651 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17652 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17653 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17654 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17655 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17656 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17657 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17658 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17659 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17660 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17661 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17662 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17663 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17664 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17665 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17666 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17667 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17668 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17669 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17670 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17671 (1, 't', 1533, 'Seats',        'The number of seats.'),
17672 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17673 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17674 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17675 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17676 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17677 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17678 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17679 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17680 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17681 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17682 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17683 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17684 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17685 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17686 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17687 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17688 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17689 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17690 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17691 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17692 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17693 (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.'),
17694 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17695 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17696 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17697 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17698 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17699 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17700 (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.'),
17701 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17702 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17703 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17704 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17705 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17706 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17707 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17708 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17709 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17710 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17711 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17712 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17713 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17714 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17715 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17716 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17717 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17718 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17719 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17720 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17721 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17722 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17723 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17724 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17725 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17726 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17727 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17728 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17729 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17730 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17731 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17732 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17733 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17734 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17735 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17736 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17737 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17738 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17739 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17740 (1, 't', 1602, 'Patients',         'Number of patients.'),
17741 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17742 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17743 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17744 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17745 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17746 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17747 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17748 (1, 't', 1610, 'Operators',        'Number of operators.'),
17749 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17750 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17751 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17752 (1, 't', 1614, 'Machines',         'Number of machines.'),
17753 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17754 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17755 (1, 't', 1617, 'Directors',        'Number of directors.'),
17756 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17757 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17758 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17759 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17760 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17761 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17762 (1, 't', 1624, 'Beds', 'Number of beds.'),
17763 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17764 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17765 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17766 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
17767 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
17768 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
17769 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
17770 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
17771 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
17772 (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.'),
17773 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
17774 (1, 't', 1636, 'Professor', 'The number of professors.'),
17775 (1, 't', 1637, 'Seller',    'The number of sellers.'),
17776 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
17777 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
17778 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
17779 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
17780 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
17781 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
17782 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
17783 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
17784 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
17785 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
17786 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
17787 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
17788 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
17789 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
17790 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
17791 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
17792 (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.'),
17793 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
17794 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
17795 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
17796 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
17797 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
17798 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
17799 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
17800 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
17801 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
17802 (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.'),
17803 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
17804 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
17805 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
17806 (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.'),
17807 (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.'),
17808 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
17809 (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.'),
17810 (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.'),
17811 (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.'),
17812 (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.'),
17813 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
17814 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
17815 (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.'),
17816 (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.'),
17817 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
17818 (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.'),
17819 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
17820 (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.'),
17821 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
17822 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
17823 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
17824 (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).'),
17825 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
17826 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
17827 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
17828 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
17829 ;
17830 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
17831
17832 CREATE TABLE acq.serial_claim (
17833     id     SERIAL           PRIMARY KEY,
17834     type   INT              NOT NULL REFERENCES acq.claim_type
17835                                      DEFERRABLE INITIALLY DEFERRED,
17836     item    BIGINT          NOT NULL REFERENCES serial.item
17837                                      DEFERRABLE INITIALLY DEFERRED
17838 );
17839
17840 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
17841
17842 CREATE TABLE acq.serial_claim_event (
17843     id             BIGSERIAL        PRIMARY KEY,
17844     type           INT              NOT NULL REFERENCES acq.claim_event_type
17845                                              DEFERRABLE INITIALLY DEFERRED,
17846     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
17847                                              DEFERRABLE INITIALLY DEFERRED,
17848     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
17849     creator        INT              NOT NULL REFERENCES actor.usr
17850                                              DEFERRABLE INITIALLY DEFERRED,
17851     note           TEXT
17852 );
17853
17854 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
17855
17856 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
17857
17858 -- now what about the auditor.*_lifecycle views??
17859
17860 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17861     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
17862 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17863     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
17864 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
17865 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
17866
17867 CREATE TABLE asset.call_number_class (
17868     id             bigserial     PRIMARY KEY,
17869     name           TEXT          NOT NULL,
17870     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
17871     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17872 );
17873
17874 COMMENT ON TABLE asset.call_number_class IS $$
17875 Defines the call number normalization database functions in the "normalizer"
17876 column and the tag/subfield combinations to use to lookup the call number in
17877 the "field" column for a given classification scheme. Tag/subfield combinations
17878 are delimited by commas.
17879 $$;
17880
17881 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
17882     ('Generic', 'asset.label_normalizer_generic'),
17883     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
17884     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
17885 ;
17886
17887 -- Generic fields
17888 UPDATE asset.call_number_class
17889     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17890     WHERE id = 1
17891 ;
17892
17893 -- Dewey fields
17894 UPDATE asset.call_number_class
17895     SET field = '080ab,082ab'
17896     WHERE id = 2
17897 ;
17898
17899 -- LC fields
17900 UPDATE asset.call_number_class
17901     SET field = '050ab,055ab'
17902     WHERE id = 3
17903 ;
17904  
17905 ALTER TABLE asset.call_number
17906         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
17907                 REFERENCES asset.call_number_class(id)
17908                 DEFERRABLE INITIALLY DEFERRED;
17909
17910 ALTER TABLE asset.call_number
17911         ADD COLUMN label_sortkey TEXT;
17912
17913 CREATE INDEX asset_call_number_label_sortkey
17914         ON asset.call_number(oils_text_as_bytea(label_sortkey));
17915
17916 ALTER TABLE auditor.asset_call_number_history
17917         ADD COLUMN label_class BIGINT;
17918
17919 ALTER TABLE auditor.asset_call_number_history
17920         ADD COLUMN label_sortkey TEXT;
17921
17922 -- Pick up the new columns in dependent views
17923
17924 DROP VIEW auditor.asset_call_number_lifecycle;
17925
17926 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17927
17928 DROP VIEW auditor.asset_call_number_lifecycle;
17929
17930 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17931
17932 DROP VIEW IF EXISTS stats.fleshed_call_number;
17933
17934 CREATE VIEW stats.fleshed_call_number AS
17935         SELECT  cn.*,
17936             CAST(cn.create_date AS DATE) AS create_date_day,
17937         CAST(cn.edit_date AS DATE) AS edit_date_day,
17938         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
17939         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
17940             rd.item_lang,
17941                 rd.item_type,
17942                 rd.item_form
17943         FROM    asset.call_number cn
17944                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
17945
17946 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
17947 DECLARE
17948     sortkey        TEXT := '';
17949 BEGIN
17950     sortkey := NEW.label_sortkey;
17951
17952     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
17953        quote_literal( NEW.label ) || ')'
17954        FROM asset.call_number_class acnc
17955        WHERE acnc.id = NEW.label_class
17956        INTO sortkey;
17957
17958     NEW.label_sortkey = sortkey;
17959
17960     RETURN NEW;
17961 END;
17962 $func$ LANGUAGE PLPGSQL;
17963
17964 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
17965     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
17966     # thus could probably be considered a derived work, although nothing was
17967     # directly copied - but to err on the safe side of providing attribution:
17968     # Copyright (C) 2007 LibLime
17969     # Licensed under the GPL v2 or later
17970
17971     use strict;
17972     use warnings;
17973
17974     # Converts the callnumber to uppercase
17975     # Strips spaces from start and end of the call number
17976     # Converts anything other than letters, digits, and periods into underscores
17977     # Collapses multiple underscores into a single underscore
17978     my $callnum = uc(shift);
17979     $callnum =~ s/^\s//g;
17980     $callnum =~ s/\s$//g;
17981     $callnum =~ s/[^A-Z0-9_.]/_/g;
17982     $callnum =~ s/_{2,}/_/g;
17983
17984     return $callnum;
17985 $func$ LANGUAGE PLPERLU;
17986
17987 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
17988     # Derived from the Koha C4::ClassSortRoutine::Dewey module
17989     # Copyright (C) 2007 LibLime
17990     # Licensed under the GPL v2 or later
17991
17992     use strict;
17993     use warnings;
17994
17995     my $init = uc(shift);
17996     $init =~ s/^\s+//;
17997     $init =~ s/\s+$//;
17998     $init =~ s!/!!g;
17999     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
18000     my @tokens = split /\.|\s+/, $init;
18001     my $digit_group_count = 0;
18002     for (my $i = 0; $i <= $#tokens; $i++) {
18003         if ($tokens[$i] =~ /^\d+$/) {
18004             $digit_group_count++;
18005             if (2 == $digit_group_count) {
18006                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
18007                 $tokens[$i] =~ tr/ /0/;
18008             }
18009         }
18010     }
18011     my $key = join("_", @tokens);
18012     $key =~ s/[^\p{IsAlnum}_]//g;
18013
18014     return $key;
18015
18016 $func$ LANGUAGE PLPERLU;
18017
18018 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
18019     use strict;
18020     use warnings;
18021
18022     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
18023     # The author hopes to upload it to CPAN some day, which would make our lives easier
18024     use Library::CallNumber::LC;
18025
18026     my $callnum = Library::CallNumber::LC->new(shift);
18027     return $callnum->normalize();
18028
18029 $func$ LANGUAGE PLPERLU;
18030
18031 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$
18032 DECLARE
18033     ans RECORD;
18034     trans INT;
18035 BEGIN
18036     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;
18037
18038     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
18039         RETURN QUERY
18040         SELECT  ans.depth,
18041                 ans.id,
18042                 COUNT( av.id ),
18043                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18044                 COUNT( av.id ),
18045                 trans
18046           FROM
18047                 actor.org_unit_descendants(ans.id) d
18048                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18049                 JOIN asset.copy cp ON (cp.id = av.id)
18050           GROUP BY 1,2,6;
18051
18052         IF NOT FOUND THEN
18053             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18054         END IF;
18055
18056     END LOOP;
18057
18058     RETURN;
18059 END;
18060 $f$ LANGUAGE PLPGSQL;
18061
18062 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$
18063 DECLARE
18064     ans RECORD;
18065     trans INT;
18066 BEGIN
18067     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;
18068
18069     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18070         RETURN QUERY
18071         SELECT  -1,
18072                 ans.id,
18073                 COUNT( av.id ),
18074                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18075                 COUNT( av.id ),
18076                 trans
18077           FROM
18078                 actor.org_unit_descendants(ans.id) d
18079                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18080                 JOIN asset.copy cp ON (cp.id = av.id)
18081           GROUP BY 1,2,6;
18082
18083         IF NOT FOUND THEN
18084             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18085         END IF;
18086
18087     END LOOP;
18088
18089     RETURN;
18090 END;
18091 $f$ LANGUAGE PLPGSQL;
18092
18093 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$
18094 DECLARE
18095     ans RECORD;
18096     trans INT;
18097 BEGIN
18098     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;
18099
18100     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
18101         RETURN QUERY
18102         SELECT  ans.depth,
18103                 ans.id,
18104                 COUNT( cp.id ),
18105                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18106                 COUNT( cp.id ),
18107                 trans
18108           FROM
18109                 actor.org_unit_descendants(ans.id) d
18110                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18111                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18112           GROUP BY 1,2,6;
18113
18114         IF NOT FOUND THEN
18115             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18116         END IF;
18117
18118     END LOOP;
18119
18120     RETURN;
18121 END;
18122 $f$ LANGUAGE PLPGSQL;
18123
18124 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$
18125 DECLARE
18126     ans RECORD;
18127     trans INT;
18128 BEGIN
18129     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;
18130
18131     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18132         RETURN QUERY
18133         SELECT  -1,
18134                 ans.id,
18135                 COUNT( cp.id ),
18136                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18137                 COUNT( cp.id ),
18138                 trans
18139           FROM
18140                 actor.org_unit_descendants(ans.id) d
18141                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18142                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18143           GROUP BY 1,2,6;
18144
18145         IF NOT FOUND THEN
18146             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18147         END IF;
18148
18149     END LOOP;
18150
18151     RETURN;
18152 END;
18153 $f$ LANGUAGE PLPGSQL;
18154
18155 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$
18156 BEGIN
18157     IF staff IS TRUE THEN
18158         IF place > 0 THEN
18159             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
18160         ELSE
18161             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
18162         END IF;
18163     ELSE
18164         IF place > 0 THEN
18165             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18166         ELSE
18167             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18168         END IF;
18169     END IF;
18170
18171     RETURN;
18172 END;
18173 $f$ LANGUAGE PLPGSQL;
18174
18175 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$
18176 DECLARE
18177     ans RECORD;
18178     trans INT;
18179 BEGIN
18180     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;
18181
18182     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
18183         RETURN QUERY
18184         SELECT  ans.depth,
18185                 ans.id,
18186                 COUNT( av.id ),
18187                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18188                 COUNT( av.id ),
18189                 trans
18190           FROM
18191                 actor.org_unit_descendants(ans.id) d
18192                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18193                 JOIN asset.copy cp ON (cp.id = av.id)
18194                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18195           GROUP BY 1,2,6;
18196
18197         IF NOT FOUND THEN
18198             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18199         END IF;
18200
18201     END LOOP;
18202
18203     RETURN;
18204 END;
18205 $f$ LANGUAGE PLPGSQL;
18206
18207 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$
18208 DECLARE
18209     ans RECORD;
18210     trans INT;
18211 BEGIN
18212     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;
18213
18214     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18215         RETURN QUERY
18216         SELECT  -1,
18217                 ans.id,
18218                 COUNT( av.id ),
18219                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18220                 COUNT( av.id ),
18221                 trans
18222           FROM
18223                 actor.org_unit_descendants(ans.id) d
18224                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18225                 JOIN asset.copy cp ON (cp.id = av.id)
18226                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18227           GROUP BY 1,2,6;
18228
18229         IF NOT FOUND THEN
18230             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18231         END IF;
18232
18233     END LOOP;
18234
18235     RETURN;
18236 END;
18237 $f$ LANGUAGE PLPGSQL;
18238
18239 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$
18240 DECLARE
18241     ans RECORD;
18242     trans INT;
18243 BEGIN
18244     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;
18245
18246     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
18247         RETURN QUERY
18248         SELECT  ans.depth,
18249                 ans.id,
18250                 COUNT( cp.id ),
18251                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18252                 COUNT( cp.id ),
18253                 trans
18254           FROM
18255                 actor.org_unit_descendants(ans.id) d
18256                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18257                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18258                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18259           GROUP BY 1,2,6;
18260
18261         IF NOT FOUND THEN
18262             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18263         END IF;
18264
18265     END LOOP;
18266
18267     RETURN;
18268 END;
18269 $f$ LANGUAGE PLPGSQL;
18270
18271 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$
18272 DECLARE
18273     ans RECORD;
18274     trans INT;
18275 BEGIN
18276     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;
18277
18278     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18279         RETURN QUERY
18280         SELECT  -1,
18281                 ans.id,
18282                 COUNT( cp.id ),
18283                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18284                 COUNT( cp.id ),
18285                 trans
18286           FROM
18287                 actor.org_unit_descendants(ans.id) d
18288                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18289                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18290                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18291           GROUP BY 1,2,6;
18292
18293         IF NOT FOUND THEN
18294             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18295         END IF;
18296
18297     END LOOP;
18298
18299     RETURN;
18300 END;
18301 $f$ LANGUAGE PLPGSQL;
18302
18303 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$
18304 BEGIN
18305     IF staff IS TRUE THEN
18306         IF place > 0 THEN
18307             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18308         ELSE
18309             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18310         END IF;
18311     ELSE
18312         IF place > 0 THEN
18313             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18314         ELSE
18315             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18316         END IF;
18317     END IF;
18318
18319     RETURN;
18320 END;
18321 $f$ LANGUAGE PLPGSQL;
18322
18323 -- No transaction is required
18324
18325 -- Triggers on the vandelay.queued_*_record tables delete entries from
18326 -- the associated vandelay.queued_*_record_attr tables based on the record's
18327 -- ID; create an index on that column to avoid sequential scans for each
18328 -- queued record that is deleted
18329 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18330 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18331
18332 -- Avoid sequential scans for queue retrieval operations by providing an
18333 -- index on the queue column
18334 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18335 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18336
18337 -- Start picking up call number label prefixes and suffixes
18338 -- from asset.copy_location
18339 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18340 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18341
18342 DROP VIEW auditor.asset_copy_lifecycle;
18343
18344 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18345
18346 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18347
18348 -- Let's not break existing reports
18349 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18350 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18351
18352 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18353 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18354 SELECT  r.id,
18355     r.fingerprint,
18356     r.quality,
18357     r.tcn_source,
18358     r.tcn_value,
18359     FIRST(title.value) AS title,
18360     FIRST(author.value) AS author,
18361     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18362     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18363     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18364     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18365   FROM  biblio.record_entry r
18366     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18367     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18368     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18369     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18370     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18371     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18372   GROUP BY 1,2,3,4,5;
18373
18374 -- Correct the ISSN array definition for reporter.simple_record
18375
18376 CREATE OR REPLACE VIEW reporter.simple_record AS
18377 SELECT  r.id,
18378         s.metarecord,
18379         r.fingerprint,
18380         r.quality,
18381         r.tcn_source,
18382         r.tcn_value,
18383         title.value AS title,
18384         uniform_title.value AS uniform_title,
18385         author.value AS author,
18386         publisher.value AS publisher,
18387         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18388         series_title.value AS series_title,
18389         series_statement.value AS series_statement,
18390         summary.value AS summary,
18391         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18392         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18393         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18394         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18395         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18396         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18397         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18398         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
18399   FROM  biblio.record_entry r
18400         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18401         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18402         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18403         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18404         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18405         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18406         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18407         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18408         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')
18409         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18410         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18411   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18412
18413 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18414     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
18415 $$ LANGUAGE SQL;
18416
18417 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18418 BEGIN
18419     IF TG_OP = 'DELETE' THEN
18420         PERFORM reporter.simple_rec_delete(NEW.id);
18421     ELSE
18422         PERFORM reporter.simple_rec_update(NEW.id);
18423     END IF;
18424
18425     RETURN NEW;
18426 END;
18427 $func$ LANGUAGE PLPGSQL;
18428
18429 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
18430
18431 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18432
18433 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18434
18435 UPDATE config.org_unit_setting_type
18436     SET view_perm = (SELECT id FROM permission.perm_list
18437         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18438     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18439
18440 UPDATE config.org_unit_setting_type
18441     SET update_perm = (SELECT id FROM permission.perm_list
18442         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18443     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18444
18445 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18446     VALUES (
18447         'opac.fully_compressed_serial_holdings',
18448         'OPAC: Use fully compressed serial holdings',
18449         'Show fully compressed serial holdings for all libraries at and below
18450         the current context unit',
18451         'bool'
18452     );
18453
18454 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18455     use strict;
18456     use warnings;
18457
18458     use utf8;
18459     use MARC::Record;
18460     use MARC::File::XML (BinaryEncoding => 'UTF8');
18461     use UUID::Tiny ':std';
18462
18463     my $xml = shift() or return undef;
18464
18465     my $r;
18466
18467     # Prevent errors in XML parsing from blowing out ungracefully
18468     eval {
18469         $r = MARC::Record->new_from_xml( $xml );
18470         1;
18471     } or do {
18472        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18473     };
18474
18475     if (!$r) {
18476        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18477     }
18478
18479     # From http://www.loc.gov/standards/sourcelist/subject.html
18480     my $thes_code_map = {
18481         a => 'lcsh',
18482         b => 'lcshac',
18483         c => 'mesh',
18484         d => 'nal',
18485         k => 'cash',
18486         n => 'notapplicable',
18487         r => 'aat',
18488         s => 'sears',
18489         v => 'rvm',
18490     };
18491
18492     # Default to "No attempt to code" if the leader is horribly broken
18493     my $fixed_field = $r->field('008');
18494     my $thes_char = '|';
18495     if ($fixed_field) {
18496         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18497     }
18498
18499     my $thes_code = 'UNDEFINED';
18500
18501     if ($thes_char eq 'z') {
18502         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18503         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18504     } elsif ($thes_code_map->{$thes_char}) {
18505         $thes_code = $thes_code_map->{$thes_char};
18506     }
18507
18508     my $auth_txt = '';
18509     my $head = $r->field('1..');
18510     if ($head) {
18511         # Concatenate all of these subfields together, prefixed by their code
18512         # to prevent collisions along the lines of "Fiction, North Carolina"
18513         foreach my $sf ($head->subfields()) {
18514             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18515         }
18516     }
18517
18518     # Perhaps better to parameterize the spi and pass as a parameter
18519     $auth_txt =~ s/'//go;
18520
18521     if ($auth_txt) {
18522         my $result = spi_exec_query("SELECT public.naco_normalize('$auth_txt') AS norm_text");
18523         my $norm_txt = $result->{rows}[0]->{norm_text};
18524         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18525     }
18526
18527     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18528 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18529
18530 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18531 /**
18532 * Extract the authority heading, thesaurus, and NACO-normalized values
18533 * from an authority record. The primary purpose is to build a unique
18534 * index to defend against duplicated authority records from the same
18535 * thesaurus.
18536 */
18537 $$;
18538
18539 DROP INDEX authority.authority_record_unique_tcn;
18540 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18541 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18542
18543 ALTER TABLE acq.provider_contact
18544         ALTER COLUMN name SET NOT NULL;
18545
18546 ALTER TABLE actor.stat_cat
18547         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18548
18549 -- Recreate some foreign keys that were somehow dropped, probably
18550 -- by some kind of cascade from an inherited table:
18551
18552 ALTER TABLE action.reservation_transit_copy
18553         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
18554                 REFERENCES booking.resource(id)
18555                 ON DELETE CASCADE
18556                 DEFERRABLE INITIALLY DEFERRED,
18557         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
18558                 REFERENCES booking.reservation(id)
18559                 ON DELETE SET NULL
18560                 DEFERRABLE INITIALLY DEFERRED;
18561
18562 CREATE INDEX user_bucket_item_target_user_idx
18563         ON container.user_bucket_item ( target_user );
18564
18565 CREATE INDEX m_c_t_collector_idx
18566         ON money.collections_tracker ( collector );
18567
18568 CREATE INDEX aud_actor_usr_address_hist_id_idx
18569         ON auditor.actor_usr_address_history ( id );
18570
18571 CREATE INDEX aud_actor_usr_hist_id_idx
18572         ON auditor.actor_usr_history ( id );
18573
18574 CREATE INDEX aud_asset_cn_hist_creator_idx
18575         ON auditor.asset_call_number_history ( creator );
18576
18577 CREATE INDEX aud_asset_cn_hist_editor_idx
18578         ON auditor.asset_call_number_history ( editor );
18579
18580 CREATE INDEX aud_asset_cp_hist_creator_idx
18581         ON auditor.asset_copy_history ( creator );
18582
18583 CREATE INDEX aud_asset_cp_hist_editor_idx
18584         ON auditor.asset_copy_history ( editor );
18585
18586 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18587         ON auditor.biblio_record_entry_history ( creator );
18588
18589 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18590         ON auditor.biblio_record_entry_history ( editor );
18591
18592 CREATE TABLE action.hold_request_note (
18593
18594     id     BIGSERIAL PRIMARY KEY,
18595     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18596                               ON DELETE CASCADE
18597                               DEFERRABLE INITIALLY DEFERRED,
18598     title  TEXT      NOT NULL,
18599     body   TEXT      NOT NULL,
18600     slip   BOOL      NOT NULL DEFAULT FALSE,
18601     pub    BOOL      NOT NULL DEFAULT FALSE,
18602     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18603
18604 );
18605 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18606
18607 -- Tweak a constraint to add a CASCADE
18608
18609 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18610
18611 ALTER TABLE action.hold_notification
18612         ADD CONSTRAINT hold_notification_hold_fkey
18613                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18614                 ON DELETE CASCADE
18615                 DEFERRABLE INITIALLY DEFERRED;
18616
18617 CREATE TRIGGER asset_label_sortkey_trigger
18618     BEFORE UPDATE OR INSERT ON asset.call_number
18619     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18620
18621 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18622 RETURNS VOID AS $$
18623 --
18624 -- Delete expired circulation bucket items for all users that have
18625 -- a setting for patron.max_reading_list_interval.
18626 --
18627 DECLARE
18628     today        TIMESTAMP WITH TIME ZONE;
18629     threshold    TIMESTAMP WITH TIME ZONE;
18630         usr_setting  RECORD;
18631 BEGIN
18632         SELECT date_trunc( 'day', now() ) INTO today;
18633         --
18634         FOR usr_setting in
18635                 SELECT
18636                         usr,
18637                         value
18638                 FROM
18639                         actor.usr_setting
18640                 WHERE
18641                         name = 'patron.max_reading_list_interval'
18642         LOOP
18643                 --
18644                 -- Make sure the setting is a valid interval
18645                 --
18646                 BEGIN
18647                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18648                 EXCEPTION
18649                         WHEN OTHERS THEN
18650                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18651                                         usr_setting.usr, usr_setting.value;
18652                                 CONTINUE;
18653                 END;
18654                 --
18655                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18656                 --
18657         DELETE FROM container.copy_bucket_item
18658         WHERE
18659                 bucket IN
18660                 (
18661                     SELECT
18662                         id
18663                     FROM
18664                         container.copy_bucket
18665                     WHERE
18666                         owner = usr_setting.usr
18667                         AND btype = 'circ_history'
18668                 )
18669                 AND create_time < threshold;
18670         END LOOP;
18671         --
18672 END;
18673 $$ LANGUAGE plpgsql;
18674
18675 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18676 /*
18677  * Delete expired circulation bucket items for all users that have
18678  * a setting for patron.max_reading_list_interval.
18679 */
18680 $$;
18681
18682 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18683          ac_usr IN INTEGER
18684 ) RETURNS VOID AS $$
18685 --
18686 -- Delete old circulation bucket items for a specified user.
18687 -- "Old" means older than the interval specified by a
18688 -- user-level setting, if it is so specified.
18689 --
18690 DECLARE
18691     threshold TIMESTAMP WITH TIME ZONE;
18692 BEGIN
18693         -- Sanity check
18694         IF ac_usr IS NULL THEN
18695                 RETURN;
18696         END IF;
18697         -- Determine the threshold date that defines "old".  Subtract the
18698         -- interval from the system date, then truncate to midnight.
18699         SELECT
18700                 date_trunc( 
18701                         'day',
18702                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18703                 )
18704         INTO
18705                 threshold
18706         FROM
18707                 actor.usr_setting
18708         WHERE
18709                 usr = ac_usr
18710                 AND name = 'patron.max_reading_list_interval';
18711         --
18712         IF threshold is null THEN
18713                 -- No interval defined; don't delete anything
18714                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18715                 return;
18716         END IF;
18717         --
18718         -- RAISE NOTICE 'Date threshold: %', threshold;
18719         --
18720         -- Threshold found; do the delete
18721         delete from container.copy_bucket_item
18722         where
18723                 bucket in
18724                 (
18725                         select
18726                                 id
18727                         from
18728                                 container.copy_bucket
18729                         where
18730                                 owner = ac_usr
18731                                 and btype = 'circ_history'
18732                 )
18733                 and create_time < threshold;
18734         --
18735         RETURN;
18736 END;
18737 $$ LANGUAGE plpgsql;
18738
18739 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18740 /*
18741  * Delete old circulation bucket items for a specified user.
18742  * "Old" means older than the interval specified by a
18743  * user-level setting, if it is so specified.
18744 */
18745 $$;
18746
18747 CREATE OR REPLACE VIEW reporter.hold_request_record AS
18748 SELECT  id,
18749     target,
18750     hold_type,
18751     CASE
18752         WHEN hold_type = 'T'
18753             THEN target
18754         WHEN hold_type = 'I'
18755             THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
18756         WHEN hold_type = 'V'
18757             THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
18758         WHEN hold_type IN ('C','R','F')
18759             THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
18760         WHEN hold_type = 'M'
18761             THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
18762     END AS bib_record
18763   FROM  action.hold_request ahr;
18764
18765 UPDATE  metabib.rec_descriptor
18766   SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
18767         date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
18768
18769 -- Change some ints to bigints:
18770
18771 ALTER TABLE container.biblio_record_entry_bucket_item
18772         ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
18773
18774 ALTER TABLE vandelay.queued_bib_record
18775         ALTER COLUMN imported_as SET DATA TYPE bigint;
18776
18777 ALTER TABLE action.hold_copy_map
18778         ALTER COLUMN id SET DATA TYPE bigint;
18779
18780 -- Make due times get pushed to 23:59:59 on insert OR update
18781 DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
18782 CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
18783
18784 COMMIT;
18785
18786 -- Some operations go outside of the transaction, because they may
18787 -- legitimately fail.
18788
18789 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
18790 \qecho doesn't exist; ignore those errors if they occur.
18791
18792 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
18793
18794 ALTER TABLE auditor.action_hold_request_history
18795 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
18796
18797 ALTER TABLE auditor.action_hold_request_history
18798 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
18799
18800 \qecho Outside of the transaction: adding indexes that may or may not exist.
18801 \qecho If any of these CREATE INDEX statements fails because the index already
18802 \qecho exists, ignore the failure.
18803
18804 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
18805 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
18806 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
18807 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
18808 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
18809 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
18810 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
18811 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
18812 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
18813 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
18814 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
18815 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
18816 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
18817 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
18818 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
18819 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
18820 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
18821 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
18822 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
18823 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
18824 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
18825 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
18826 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
18827 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
18828 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
18829 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
18830 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
18831 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
18832 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
18833 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
18834 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
18835 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
18836 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
18837
18838 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
18839
18840 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
18841
18842 \qecho If the following CREATE INDEX fails, It will be necessary to do some
18843 \qecho data cleanup as described in the comments.
18844
18845 CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
18846     ON authority.record_entry (authority.normalize_heading(marc))
18847         WHERE deleted IS FALSE or deleted = FALSE;
18848
18849 -- If the unique index fails, uncomment the following to create
18850 -- a regular index that will help find the duplicates in a hurry:
18851 --CREATE INDEX by_heading_and_thesaurus
18852 --    ON authority.record_entry (authority.normalize_heading(marc))
18853 --    WHERE deleted IS FALSE or deleted = FALSE
18854 --;
18855
18856 -- Then find the duplicates like so to get an idea of how much
18857 -- pain you're looking at to clean things up:
18858 --SELECT id, authority.normalize_heading(marc)
18859 --    FROM authority.record_entry
18860 --    WHERE authority.normalize_heading(marc) IN (
18861 --        SELECT authority.normalize_heading(marc)
18862 --        FROM authority.record_entry
18863 --        GROUP BY authority.normalize_heading(marc)
18864 --        HAVING COUNT(*) > 1
18865 --    )
18866 --;
18867
18868 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
18869 -- statement succeeds, drop the temporary index to avoid unnecessary
18870 -- duplication:
18871 -- DROP INDEX authority.by_heading_and_thesaurus;
18872
18873 \qecho Upgrade script completed.