]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
remove aupr.request_time fix from 1.6.1-2.0 upgrade
[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 ('0453');
27
28 -- Remove some uses of the connectby() function from the tablefunc contrib module
29 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT, INT ) RETURNS SETOF actor.org_unit AS $$
30     WITH RECURSIVE descendant_depth AS (
31         SELECT  ou.id,
32                 ou.parent_ou,
33                 out.depth
34           FROM  actor.org_unit ou
35                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
36                 JOIN anscestor_depth ad ON (ad.id = ou.id)
37           WHERE ad.depth = $2
38             UNION ALL
39         SELECT  ou.id,
40                 ou.parent_ou,
41                 out.depth
42           FROM  actor.org_unit ou
43                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
44                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
45     ), anscestor_depth AS (
46         SELECT  ou.id,
47                 ou.parent_ou,
48                 out.depth
49           FROM  actor.org_unit ou
50                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
51           WHERE ou.id = $1
52             UNION ALL
53         SELECT  ou.id,
54                 ou.parent_ou,
55                 out.depth
56           FROM  actor.org_unit ou
57                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
58                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
59     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
60 $$ LANGUAGE SQL;
61
62 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT ) RETURNS SETOF actor.org_unit AS $$
63     WITH RECURSIVE descendant_depth AS (
64         SELECT  ou.id,
65                 ou.parent_ou,
66                 out.depth
67           FROM  actor.org_unit ou
68                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
69           WHERE ou.id = $1
70             UNION ALL
71         SELECT  ou.id,
72                 ou.parent_ou,
73                 out.depth
74           FROM  actor.org_unit ou
75                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
76                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
77     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
78 $$ LANGUAGE SQL;
79
80 CREATE OR REPLACE FUNCTION actor.org_unit_ancestors( INT ) RETURNS SETOF actor.org_unit AS $$
81     WITH RECURSIVE anscestor_depth AS (
82         SELECT  ou.id,
83                 ou.parent_ou
84           FROM  actor.org_unit ou
85           WHERE ou.id = $1
86             UNION ALL
87         SELECT  ou.id,
88                 ou.parent_ou
89           FROM  actor.org_unit ou
90                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
91     ) SELECT ou.* FROM actor.org_unit ou JOIN anscestor_depth USING (id);
92 $$ LANGUAGE SQL;
93
94 -- Support merge template buckets
95 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('template_merge','Template Merge Container');
96
97 -- Recreate one of the constraints that we just dropped,
98 -- under a different name:
99
100 ALTER TABLE booking.resource_type
101         ADD CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record);
102
103 -- Now upgrade permission.perm_list.  This is fairly complicated.
104
105 -- Add ON UPDATE CASCADE to some foreign keys so that, when we renumber the
106 -- permissions, the dependents will follow and stay in sync:
107
108 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
109     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
110
111 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_perm_map_perm_fkey FOREIGN KEY (perm)
112     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
113
114 ALTER TABLE permission.usr_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
115     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
116
117 UPDATE permission.perm_list
118     SET code = 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow'
119     WHERE code = 'UPDATE_ORG_UNIT_SETTING.global.credit.allow';
120
121 -- The following UPDATES were originally in an individual upgrade script, but should
122 -- no longer be necessary now that the foreign key has an ON UPDATE CASCADE clause.
123 -- We retain the UPDATES here, commented out, as historical relics.
124
125 -- UPDATE permission.grp_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
126 -- UPDATE permission.usr_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
127
128 -- Spelling correction
129 UPDATE permission.perm_list SET code = 'ADMIN_RECURRING_FINE_RULE' WHERE code = 'ADMIN_RECURING_FINE_RULE';
130
131 -- Now we engage in a Great Renumbering of the permissions in permission.perm_list,
132 -- in order to clean up accumulated cruft.
133
134 -- The first step is to establish some triggers so that, when we change the id of a permission,
135 -- the associated translations are updated accordingly.
136
137 CREATE OR REPLACE FUNCTION oils_i18n_update_apply(old_ident TEXT, new_ident TEXT, hint TEXT) RETURNS VOID AS $_$
138 BEGIN
139
140     EXECUTE $$
141         UPDATE  config.i18n_core
142           SET   identity_value = $$ || quote_literal( new_ident ) || $$ 
143           WHERE fq_field LIKE '$$ || hint || $$.%' 
144                 AND identity_value = $$ || quote_literal( old_ident ) || $$;$$;
145
146     RETURN;
147
148 END;
149 $_$ LANGUAGE PLPGSQL;
150
151 CREATE OR REPLACE FUNCTION oils_i18n_id_tracking(/* hint */) RETURNS TRIGGER AS $_$
152 BEGIN
153     PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
154     RETURN NEW;
155 END;
156 $_$ LANGUAGE PLPGSQL;
157
158 CREATE OR REPLACE FUNCTION oils_i18n_code_tracking(/* hint */) RETURNS TRIGGER AS $_$
159 BEGIN
160     PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
161     RETURN NEW;
162 END;
163 $_$ LANGUAGE PLPGSQL;
164
165
166 CREATE TRIGGER maintain_perm_i18n_tgr
167     AFTER UPDATE ON permission.perm_list
168     FOR EACH ROW EXECUTE PROCEDURE oils_i18n_id_tracking('ppl');
169
170 -- Next, create a new table as a convenience for sloshing data back and forth,
171 -- and for recording which permission went where.  It looks just like
172 -- permission.perm_list, but with two extra columns: one for the old id, and one to
173 -- distinguish between predefined permissions and non-predefined permissions.
174
175 -- This table is, in effect, a temporary table, because we can drop it once the
176 -- upgrade is complete.  It is not technically temporary as far as PostgreSQL is
177 -- concerned, because we don't want it to disappear at the end of the session.
178 -- We keep it around so that we have a map showing the old id and the new id for
179 -- each permission.  However there is no IDL entry for it, nor is it defined
180 -- in the base sql files.
181
182 CREATE TABLE permission.temp_perm (
183         id          INT        PRIMARY KEY,
184         code        TEXT       UNIQUE,
185         description TEXT,
186         old_id      INT,
187         predefined  BOOL       NOT NULL DEFAULT TRUE
188 );
189
190 -- Populate the temp table with a definitive set of predefined permissions,
191 -- hard-coding the ids.
192
193 -- The first set of permissions is derived from the database, as loaded in a
194 -- loaded 1.6.1 database, plus a few changes previously applied in this upgrade
195 -- script.  The second set is derived from the IDL -- permissions that are referenced
196 -- in <permacrud> elements but not defined in the database.
197
198 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( -1, 'EVERYTHING',
199      '' );
200 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 1, 'OPAC_LOGIN',
201      'Allow a user to log in to the OPAC' );
202 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 2, 'STAFF_LOGIN',
203      'Allow a user to log in to the staff client' );
204 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 3, 'MR_HOLDS',
205      'Allow a user to create a metarecord holds' );
206 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 4, 'TITLE_HOLDS',
207      'Allow a user to place a hold at the title level' );
208 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 5, 'VOLUME_HOLDS',
209      'Allow a user to place a volume level hold' );
210 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 6, 'COPY_HOLDS',
211      'Allow a user to place a hold on a specific copy' );
212 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 7, 'REQUEST_HOLDS',
213      'Allow a user to create holds for another user (if true, we still check to make sure they have permission to make the type of hold they are requesting, for example, COPY_HOLDS)' );
214 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 8, 'REQUEST_HOLDS_OVERRIDE',
215      '* no longer applicable' );
216 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 9, 'VIEW_HOLD',
217      'Allow a user to view another user''s holds' );
218 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 10, 'DELETE_HOLDS',
219      '* no longer applicable' );
220 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 11, 'UPDATE_HOLD',
221      'Allow a user to update another user''s hold' );
222 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 12, 'RENEW_CIRC',
223      'Allow a user to renew items' );
224 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 13, 'VIEW_USER_FINES_SUMMARY',
225      'Allow a user to view bill details' );
226 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 14, 'VIEW_USER_TRANSACTIONS',
227      'Allow a user to see another user''s grocery or circulation transactions in the Bills Interface; duplicate of VIEW_TRANSACTION' );
228 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 15, 'UPDATE_MARC',
229      'Allow a user to edit a MARC record' );
230 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 16, 'CREATE_MARC',
231      'Allow a user to create new MARC records' );
232 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 17, 'IMPORT_MARC',
233      'Allow a user to import a MARC record via the Z39.50 interface' );
234 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 18, 'CREATE_VOLUME',
235      'Allow a user to create a volume' );
236 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 19, 'UPDATE_VOLUME',
237      'Allow a user to edit volumes - needed for merging records. This is a duplicate of VOLUME_UPDATE; user must have both permissions at appropriate level to merge records.' );
238 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 20, 'DELETE_VOLUME',
239      'Allow a user to delete a volume' );
240 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 21, 'CREATE_COPY',
241      'Allow a user to create a new copy object' );
242 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 22, 'UPDATE_COPY',
243      'Allow a user to edit a copy' );
244 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 23, 'DELETE_COPY',
245      'Allow a user to delete a copy' );
246 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 24, 'RENEW_HOLD_OVERRIDE',
247      'Allow a user to continue to renew an item even if it is required for a hold' );
248 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 25, 'CREATE_USER',
249      'Allow a user to create another user' );
250 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 26, 'UPDATE_USER',
251      'Allow a user to edit a user''s record' );
252 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 27, 'DELETE_USER',
253      'Allow a user to mark a user as deleted' );
254 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 28, 'VIEW_USER',
255      'Allow a user to view another user''s Patron Record' );
256 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 29, 'COPY_CHECKIN',
257      'Allow a user to check in a copy' );
258 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 30, 'CREATE_TRANSIT',
259      'Allow a user to place an item in transit' );
260 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 31, 'VIEW_PERMISSION',
261      'Allow a user to view user permissions within the user permissions editor' );
262 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 32, 'CHECKIN_BYPASS_HOLD_FULFILL',
263      '* no longer applicable' );
264 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 33, 'CREATE_PAYMENT',
265      'Allow a user to record payments in the Billing Interface' );
266 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 34, 'SET_CIRC_LOST',
267      'Allow a user to mark an item as ''lost''' );
268 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 35, 'SET_CIRC_MISSING',
269      'Allow a user to mark an item as ''missing''' );
270 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 36, 'SET_CIRC_CLAIMS_RETURNED',
271      'Allow a user to mark an item as ''claims returned''' );
272 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 37, 'CREATE_TRANSACTION',
273      'Allow a user to create a new billable transaction' );
274 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 38, 'VIEW_TRANSACTION',
275      'Allow a user may view another user''s transactions' );
276 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 39, 'CREATE_BILL',
277      'Allow a user to create a new bill on a transaction' );
278 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 40, 'VIEW_CONTAINER',
279      'Allow a user to view another user''s containers (buckets)' );
280 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 41, 'CREATE_CONTAINER',
281      'Allow a user to create a new container for another user' );
282 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 42, 'UPDATE_ORG_UNIT',
283      'Allow a user to change the settings for an organization unit' );
284 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 43, 'VIEW_CIRCULATIONS',
285      'Allow a user to see what another user has checked out' );
286 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 44, 'DELETE_CONTAINER',
287      'Allow a user to delete another user''s container' );
288 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 45, 'CREATE_CONTAINER_ITEM',
289      'Allow a user to create a container item for another user' );
290 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 46, 'CREATE_USER_GROUP_LINK',
291      'Allow a user to add other users to permission groups' );
292 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 47, 'REMOVE_USER_GROUP_LINK',
293      'Allow a user to remove other users from permission groups' );
294 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 48, 'VIEW_PERM_GROUPS',
295      'Allow a user to view other users'' permission groups' );
296 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 49, 'VIEW_PERMIT_CHECKOUT',
297      'Allow a user to determine whether another user can check out an item' );
298 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 50, 'UPDATE_BATCH_COPY',
299      'Allow a user to edit copies in batch' );
300 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 51, 'CREATE_PATRON_STAT_CAT',
301      'User may create a new patron statistical category' );
302 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 52, 'CREATE_COPY_STAT_CAT',
303      'User may create a copy statistical category' );
304 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 53, 'CREATE_PATRON_STAT_CAT_ENTRY',
305      'User may create an entry in a patron statistical category' );
306 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 54, 'CREATE_COPY_STAT_CAT_ENTRY',
307      'User may create an entry in a copy statistical category' );
308 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 55, 'UPDATE_PATRON_STAT_CAT',
309      'User may update a patron statistical category' );
310 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 56, 'UPDATE_COPY_STAT_CAT',
311      'User may update a copy statistical category' );
312 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 57, 'UPDATE_PATRON_STAT_CAT_ENTRY',
313      'User may update an entry in a patron statistical category' );
314 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 58, 'UPDATE_COPY_STAT_CAT_ENTRY',
315      'User may update an entry in a copy statistical category' );
316 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 59, 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
317      'User may link another user to an entry in a statistical category' );
318 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 60, 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
319      'User may link a copy to an entry in a statistical category' );
320 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 61, 'DELETE_PATRON_STAT_CAT',
321      'User may delete a patron statistical category' );
322 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 62, 'DELETE_COPY_STAT_CAT',
323      'User may delete a copy statistical category' );
324 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 63, 'DELETE_PATRON_STAT_CAT_ENTRY',
325      'User may delete an entry from a patron statistical category' );
326 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 64, 'DELETE_COPY_STAT_CAT_ENTRY',
327      'User may delete an entry from a copy statistical category' );
328 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 65, 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
329      'User may delete a patron statistical category entry map' );
330 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 66, 'DELETE_COPY_STAT_CAT_ENTRY_MAP',
331      'User may delete a copy statistical category entry map' );
332 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 67, 'CREATE_NON_CAT_TYPE',
333      'Allow a user to create a new non-cataloged item type' );
334 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 68, 'UPDATE_NON_CAT_TYPE',
335      'Allow a user to update a non-cataloged item type' );
336 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 69, 'CREATE_IN_HOUSE_USE',
337      'Allow a user to create a new in-house-use ' );
338 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 70, 'COPY_CHECKOUT',
339      'Allow a user to check out a copy' );
340 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 71, 'CREATE_COPY_LOCATION',
341      'Allow a user to create a new copy location' );
342 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 72, 'UPDATE_COPY_LOCATION',
343      'Allow a user to update a copy location' );
344 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 73, 'DELETE_COPY_LOCATION',
345      'Allow a user to delete a copy location' );
346 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 74, 'CREATE_COPY_TRANSIT',
347      'Allow a user to create a transit_copy object for transiting a copy' );
348 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 75, 'COPY_TRANSIT_RECEIVE',
349      'Allow a user to close out a transit on a copy' );
350 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 76, 'VIEW_HOLD_PERMIT',
351      'Allow a user to see if another user has permission to place a hold on a given copy' );
352 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 77, 'VIEW_COPY_CHECKOUT_HISTORY',
353      'Allow a user to view which users have checked out a given copy' );
354 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 78, 'REMOTE_Z3950_QUERY',
355      'Allow a user to perform Z39.50 queries against remote servers' );
356 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 79, 'REGISTER_WORKSTATION',
357      'Allow a user to register a new workstation' );
358 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 80, 'VIEW_COPY_NOTES',
359      'Allow a user to view all notes attached to a copy' );
360 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 81, 'VIEW_VOLUME_NOTES',
361      'Allow a user to view all notes attached to a volume' );
362 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 82, 'VIEW_TITLE_NOTES',
363      'Allow a user to view all notes attached to a title' );
364 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 83, 'CREATE_COPY_NOTE',
365      'Allow a user to create a new copy note' );
366 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 84, 'CREATE_VOLUME_NOTE',
367      'Allow a user to create a new volume note' );
368 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 85, 'CREATE_TITLE_NOTE',
369      'Allow a user to create a new title note' );
370 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 86, 'DELETE_COPY_NOTE',
371      'Allow a user to delete another user''s copy notes' );
372 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 87, 'DELETE_VOLUME_NOTE',
373      'Allow a user to delete another user''s volume note' );
374 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 88, 'DELETE_TITLE_NOTE',
375      'Allow a user to delete another user''s title note' );
376 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 89, 'UPDATE_CONTAINER',
377      'Allow a user to update another user''s container' );
378 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 90, 'CREATE_MY_CONTAINER',
379      'Allow a user to create a container for themselves' );
380 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 91, 'VIEW_HOLD_NOTIFICATION',
381      'Allow a user to view notifications attached to a hold' );
382 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 92, 'CREATE_HOLD_NOTIFICATION',
383      'Allow a user to create new hold notifications' );
384 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 93, 'UPDATE_ORG_SETTING',
385      'Allow a user to update an organization unit setting' );
386 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 94, 'OFFLINE_UPLOAD',
387      'Allow a user to upload an offline script' );
388 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 95, 'OFFLINE_VIEW',
389      'Allow a user to view uploaded offline script information' );
390 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 96, 'OFFLINE_EXECUTE',
391      'Allow a user to execute an offline script batch' );
392 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 97, 'CIRC_OVERRIDE_DUE_DATE',
393      'Allow a user to change the due date on an item to any date' );
394 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 98, 'CIRC_PERMIT_OVERRIDE',
395      'Allow a user to bypass the circulation permit call for check out' );
396 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 99, 'COPY_IS_REFERENCE.override',
397      'Allow a user to override the copy_is_reference event' );
398 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 100, 'VOID_BILLING',
399      'Allow a user to void a bill' );
400 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 101, 'CIRC_CLAIMS_RETURNED.override',
401      'Allow a user to check in or check out an item that has a status of ''claims returned''' );
402 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 102, 'COPY_BAD_STATUS.override',
403      'Allow a user to check out an item in a non-circulatable status' );
404 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 103, 'COPY_ALERT_MESSAGE.override',
405      'Allow a user to check in/out an item that has an alert message' );
406 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 104, 'COPY_STATUS_LOST.override',
407      'Allow a user to remove the lost status from a copy' );
408 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 105, 'COPY_STATUS_MISSING.override',
409      'Allow a user to change the missing status on a copy' );
410 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 106, 'ABORT_TRANSIT',
411      'Allow a user to abort a copy transit if the user is at the transit destination or source' );
412 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 107, 'ABORT_REMOTE_TRANSIT',
413      'Allow a user to abort a copy transit if the user is not at the transit source or dest' );
414 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 108, 'VIEW_ZIP_DATA',
415      'Allow a user to query the ZIP code data method' );
416 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 109, 'CANCEL_HOLDS',
417      'Allow a user to cancel holds' );
418 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 110, 'CREATE_DUPLICATE_HOLDS',
419      'Allow a user to create duplicate holds (two or more holds on the same title)' );
420 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 111, 'actor.org_unit.closed_date.delete',
421      'Allow a user to remove a closed date interval for a given location' );
422 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 112, 'actor.org_unit.closed_date.update',
423      'Allow a user to update a closed date interval for a given location' );
424 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 113, 'actor.org_unit.closed_date.create',
425      'Allow a user to create a new closed date for a location' );
426 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 114, 'DELETE_NON_CAT_TYPE',
427      'Allow a user to delete a non cataloged type' );
428 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 115, 'money.collections_tracker.create',
429      'Allow a user to put someone into collections' );
430 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 116, 'money.collections_tracker.delete',
431      'Allow a user to remove someone from collections' );
432 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 117, 'BAR_PATRON',
433      'Allow a user to bar a patron' );
434 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 118, 'UNBAR_PATRON',
435      'Allow a user to un-bar a patron' );
436 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 119, 'DELETE_WORKSTATION',
437      'Allow a user to remove an existing workstation so a new one can replace it' );
438 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 120, 'group_application.user',
439      'Allow a user to add/remove users to/from the "User" group' );
440 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 121, 'group_application.user.patron',
441      'Allow a user to add/remove users to/from the "Patron" group' );
442 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 122, 'group_application.user.staff',
443      'Allow a user to add/remove users to/from the "Staff" group' );
444 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 123, 'group_application.user.staff.circ',
445      'Allow a user to add/remove users to/from the "Circulator" group' );
446 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 124, 'group_application.user.staff.cat',
447      'Allow a user to add/remove users to/from the "Cataloger" group' );
448 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 125, 'group_application.user.staff.admin.global_admin',
449      'Allow a user to add/remove users to/from the "GlobalAdmin" group' );
450 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 126, 'group_application.user.staff.admin.local_admin',
451      'Allow a user to add/remove users to/from the "LocalAdmin" group' );
452 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 127, 'group_application.user.staff.admin.lib_manager',
453      'Allow a user to add/remove users to/from the "LibraryManager" group' );
454 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 128, 'group_application.user.staff.cat.cat1',
455      'Allow a user to add/remove users to/from the "Cat1" group' );
456 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 129, 'group_application.user.staff.supercat',
457      'Allow a user to add/remove users to/from the "Supercat" group' );
458 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 130, 'group_application.user.sip_client',
459      'Allow a user to add/remove users to/from the "SIP-Client" group' );
460 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 131, 'group_application.user.vendor',
461      'Allow a user to add/remove users to/from the "Vendor" group' );
462 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 132, 'ITEM_AGE_PROTECTED.override',
463      'Allow a user to place a hold on an age-protected item' );
464 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 133, 'MAX_RENEWALS_REACHED.override',
465      'Allow a user to renew an item past the maximum renewal count' );
466 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 134, 'PATRON_EXCEEDS_CHECKOUT_COUNT.override',
467      'Allow staff to override checkout count failure' );
468 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 135, 'PATRON_EXCEEDS_OVERDUE_COUNT.override',
469      'Allow staff to override overdue count failure' );
470 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 136, 'PATRON_EXCEEDS_FINES.override',
471      'Allow staff to override fine amount checkout failure' );
472 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 137, 'CIRC_EXCEEDS_COPY_RANGE.override',
473      'Allow staff to override circulation copy range failure' );
474 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 138, 'ITEM_ON_HOLDS_SHELF.override',
475      'Allow staff to override item on holds shelf failure' );
476 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 139, 'COPY_NOT_AVAILABLE.override',
477      'Allow staff to force checkout of Missing/Lost type items' );
478 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 140, 'HOLD_EXISTS.override',
479      'Allow a user to place multiple holds on a single title' );
480 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 141, 'RUN_REPORTS',
481      'Allow a user to run reports' );
482 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 142, 'SHARE_REPORT_FOLDER',
483      'Allow a user to share report his own folders' );
484 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 143, 'VIEW_REPORT_OUTPUT',
485      'Allow a user to view report output' );
486 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 144, 'COPY_CIRC_NOT_ALLOWED.override',
487      'Allow a user to checkout an item that is marked as non-circ' );
488 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 145, 'DELETE_CONTAINER_ITEM',
489      'Allow a user to delete an item out of another user''s container' );
490 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 146, 'ASSIGN_WORK_ORG_UNIT',
491      'Allow a staff member to define where another staff member has their permissions' );
492 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 147, 'CREATE_FUNDING_SOURCE',
493      'Allow a user to create a new funding source' );
494 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 148, 'DELETE_FUNDING_SOURCE',
495      'Allow a user to delete a funding source' );
496 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 149, 'VIEW_FUNDING_SOURCE',
497      'Allow a user to view a funding source' );
498 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 150, 'UPDATE_FUNDING_SOURCE',
499      'Allow a user to update a funding source' );
500 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 151, 'CREATE_FUND',
501      'Allow a user to create a new fund' );
502 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 152, 'DELETE_FUND',
503      'Allow a user to delete a fund' );
504 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 153, 'VIEW_FUND',
505      'Allow a user to view a fund' );
506 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 154, 'UPDATE_FUND',
507      'Allow a user to update a fund' );
508 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 155, 'CREATE_FUND_ALLOCATION',
509      'Allow a user to create a new fund allocation' );
510 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 156, 'DELETE_FUND_ALLOCATION',
511      'Allow a user to delete a fund allocation' );
512 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 157, 'VIEW_FUND_ALLOCATION',
513      'Allow a user to view a fund allocation' );
514 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 158, 'UPDATE_FUND_ALLOCATION',
515      'Allow a user to update a fund allocation' );
516 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 159, 'GENERAL_ACQ',
517      'Lowest level permission required to access the ACQ interface' );
518 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 160, 'CREATE_PROVIDER',
519      'Allow a user to create a new provider' );
520 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 161, 'DELETE_PROVIDER',
521      'Allow a user to delate a provider' );
522 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 162, 'VIEW_PROVIDER',
523      'Allow a user to view a provider' );
524 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 163, 'UPDATE_PROVIDER',
525      'Allow a user to update a provider' );
526 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 164, 'ADMIN_FUNDING_SOURCE',
527      'Allow a user to create/view/update/delete a funding source' );
528 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 165, 'ADMIN_FUND',
529      '(Deprecated) Allow a user to create/view/update/delete a fund' );
530 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 166, 'MANAGE_FUNDING_SOURCE',
531      'Allow a user to view/credit/debit a funding source' );
532 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 167, 'MANAGE_FUND',
533      'Allow a user to view/credit/debit a fund' );
534 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 168, 'CREATE_PICKLIST',
535      'Allows a user to create a picklist' );
536 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 169, 'ADMIN_PROVIDER',
537      'Allow a user to create/view/update/delete a provider' );
538 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 170, 'MANAGE_PROVIDER',
539      'Allow a user to view and purchase from a provider' );
540 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 171, 'VIEW_PICKLIST',
541      'Allow a user to view another users picklist' );
542 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 172, 'DELETE_RECORD',
543      'Allow a staff member to directly remove a bibliographic record' );
544 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 173, 'ADMIN_CURRENCY_TYPE',
545      'Allow a user to create/view/update/delete a currency_type' );
546 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 174, 'MARK_BAD_DEBT',
547      'Allow a user to mark a transaction as bad (unrecoverable) debt' );
548 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 175, 'VIEW_BILLING_TYPE',
549      'Allow a user to view billing types' );
550 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 176, 'MARK_ITEM_AVAILABLE',
551      'Allow a user to mark an item status as ''available''' );
552 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 177, 'MARK_ITEM_CHECKED_OUT',
553      'Allow a user to mark an item status as ''checked out''' );
554 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 178, 'MARK_ITEM_BINDERY',
555      'Allow a user to mark an item status as ''bindery''' );
556 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 179, 'MARK_ITEM_LOST',
557      'Allow a user to mark an item status as ''lost''' );
558 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 180, 'MARK_ITEM_MISSING',
559      'Allow a user to mark an item status as ''missing''' );
560 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 181, 'MARK_ITEM_IN_PROCESS',
561      'Allow a user to mark an item status as ''in process''' );
562 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 182, 'MARK_ITEM_IN_TRANSIT',
563      'Allow a user to mark an item status as ''in transit''' );
564 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 183, 'MARK_ITEM_RESHELVING',
565      'Allow a user to mark an item status as ''reshelving''' );
566 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 184, 'MARK_ITEM_ON_HOLDS_SHELF',
567      'Allow a user to mark an item status as ''on holds shelf''' );
568 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 185, 'MARK_ITEM_ON_ORDER',
569      'Allow a user to mark an item status as ''on order''' );
570 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 186, 'MARK_ITEM_ILL',
571      'Allow a user to mark an item status as ''inter-library loan''' );
572 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 187, 'group_application.user.staff.acq',
573      'Allows a user to add/remove/edit users in the "ACQ" group' );
574 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 188, 'CREATE_PURCHASE_ORDER',
575      'Allows a user to create a purchase order' );
576 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 189, 'VIEW_PURCHASE_ORDER',
577      'Allows a user to view a purchase order' );
578 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 190, 'IMPORT_ACQ_LINEITEM_BIB_RECORD',
579      'Allows a user to import a bib record from the acq staging area (on-order record) into the ILS bib data set' );
580 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 191, 'RECEIVE_PURCHASE_ORDER',
581      'Allows a user to mark a purchase order, lineitem, or individual copy as received' );
582 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 192, 'VIEW_ORG_SETTINGS',
583      'Allows a user to view all org settings at the specified level' );
584 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 193, 'CREATE_MFHD_RECORD',
585      'Allows a user to create a new MFHD record' );
586 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 194, 'UPDATE_MFHD_RECORD',
587      'Allows a user to update an MFHD record' );
588 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 195, 'DELETE_MFHD_RECORD',
589      'Allows a user to delete an MFHD record' );
590 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 196, 'ADMIN_ACQ_FUND',
591      'Allow a user to create/view/update/delete a fund' );
592 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 197, 'group_application.user.staff.acq_admin',
593      'Allows a user to add/remove/edit users in the "Acquisitions Administrators" group' );
594 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 198, 'SET_CIRC_CLAIMS_RETURNED.override',
595      'Allows staff to override the max claims returned value for a patron' );
596 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 199, 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
597      'Allows staff to manually change a patron''s claims returned count' );
598 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 200, 'UPDATE_BILL_NOTE',
599      'Allows staff to edit the note for a bill on a transaction' );
600 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 201, 'UPDATE_PAYMENT_NOTE',
601      'Allows staff to edit the note for a payment on a transaction' );
602 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 202, 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
603      'Allows staff to manually change a patron''s claims never checkout out count' );
604 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 203, 'ADMIN_COPY_LOCATION_ORDER',
605      'Allow a user to create/view/update/delete a copy location order' );
606 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 204, 'ASSIGN_GROUP_PERM',
607      '' );
608 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 205, 'CREATE_AUDIENCE',
609      '' );
610 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 206, 'CREATE_BIB_LEVEL',
611      '' );
612 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 207, 'CREATE_CIRC_DURATION',
613      '' );
614 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 208, 'CREATE_CIRC_MOD',
615      '' );
616 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 209, 'CREATE_COPY_STATUS',
617      '' );
618 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 210, 'CREATE_HOURS_OF_OPERATION',
619      '' );
620 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 211, 'CREATE_ITEM_FORM',
621      '' );
622 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 212, 'CREATE_ITEM_TYPE',
623      '' );
624 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 213, 'CREATE_LANGUAGE',
625      '' );
626 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 214, 'CREATE_LASSO',
627      '' );
628 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 215, 'CREATE_LASSO_MAP',
629      '' );
630 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 216, 'CREATE_LIT_FORM',
631      '' );
632 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 217, 'CREATE_METABIB_FIELD',
633      '' );
634 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 218, 'CREATE_NET_ACCESS_LEVEL',
635      '' );
636 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 219, 'CREATE_ORG_ADDRESS',
637      '' );
638 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 220, 'CREATE_ORG_TYPE',
639      '' );
640 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 221, 'CREATE_ORG_UNIT',
641      '' );
642 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 222, 'CREATE_ORG_UNIT_CLOSING',
643      '' );
644 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 223, 'CREATE_PERM',
645      '' );
646 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 224, 'CREATE_RELEVANCE_ADJUSTMENT',
647      '' );
648 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 225, 'CREATE_SURVEY',
649      '' );
650 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 226, 'CREATE_VR_FORMAT',
651      '' );
652 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 227, 'CREATE_XML_TRANSFORM',
653      '' );
654 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 228, 'DELETE_AUDIENCE',
655      '' );
656 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 229, 'DELETE_BIB_LEVEL',
657      '' );
658 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 230, 'DELETE_CIRC_DURATION',
659      '' );
660 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 231, 'DELETE_CIRC_MOD',
661      '' );
662 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 232, 'DELETE_COPY_STATUS',
663      '' );
664 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 233, 'DELETE_HOURS_OF_OPERATION',
665      '' );
666 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 234, 'DELETE_ITEM_FORM',
667      '' );
668 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 235, 'DELETE_ITEM_TYPE',
669      '' );
670 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 236, 'DELETE_LANGUAGE',
671      '' );
672 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 237, 'DELETE_LASSO',
673      '' );
674 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 238, 'DELETE_LASSO_MAP',
675      '' );
676 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 239, 'DELETE_LIT_FORM',
677      '' );
678 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 240, 'DELETE_METABIB_FIELD',
679      '' );
680 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 241, 'DELETE_NET_ACCESS_LEVEL',
681      '' );
682 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 242, 'DELETE_ORG_ADDRESS',
683      '' );
684 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 243, 'DELETE_ORG_TYPE',
685      '' );
686 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 244, 'DELETE_ORG_UNIT',
687      '' );
688 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 245, 'DELETE_ORG_UNIT_CLOSING',
689      '' );
690 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 246, 'DELETE_PERM',
691      '' );
692 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 247, 'DELETE_RELEVANCE_ADJUSTMENT',
693      '' );
694 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 248, 'DELETE_SURVEY',
695      '' );
696 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 249, 'DELETE_TRANSIT',
697      '' );
698 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 250, 'DELETE_VR_FORMAT',
699      '' );
700 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 251, 'DELETE_XML_TRANSFORM',
701      '' );
702 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 252, 'REMOVE_GROUP_PERM',
703      '' );
704 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 253, 'TRANSIT_COPY',
705      '' );
706 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 254, 'UPDATE_AUDIENCE',
707      '' );
708 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 255, 'UPDATE_BIB_LEVEL',
709      '' );
710 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 256, 'UPDATE_CIRC_DURATION',
711      '' );
712 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 257, 'UPDATE_CIRC_MOD',
713      '' );
714 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 258, 'UPDATE_COPY_NOTE',
715      '' );
716 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 259, 'UPDATE_COPY_STATUS',
717      '' );
718 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 260, 'UPDATE_GROUP_PERM',
719      '' );
720 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 261, 'UPDATE_HOURS_OF_OPERATION',
721      '' );
722 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 262, 'UPDATE_ITEM_FORM',
723      '' );
724 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 263, 'UPDATE_ITEM_TYPE',
725      '' );
726 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 264, 'UPDATE_LANGUAGE',
727      '' );
728 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 265, 'UPDATE_LASSO',
729      '' );
730 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 266, 'UPDATE_LASSO_MAP',
731      '' );
732 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 267, 'UPDATE_LIT_FORM',
733      '' );
734 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 268, 'UPDATE_METABIB_FIELD',
735      '' );
736 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 269, 'UPDATE_NET_ACCESS_LEVEL',
737      '' );
738 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 270, 'UPDATE_ORG_ADDRESS',
739      '' );
740 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 271, 'UPDATE_ORG_TYPE',
741      '' );
742 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 272, 'UPDATE_ORG_UNIT_CLOSING',
743      '' );
744 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 273, 'UPDATE_PERM',
745      '' );
746 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 274, 'UPDATE_RELEVANCE_ADJUSTMENT',
747      '' );
748 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 275, 'UPDATE_SURVEY',
749      '' );
750 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 276, 'UPDATE_TRANSIT',
751      '' );
752 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 277, 'UPDATE_VOLUME_NOTE',
753      '' );
754 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 278, 'UPDATE_VR_FORMAT',
755      '' );
756 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 279, 'UPDATE_XML_TRANSFORM',
757      '' );
758 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 280, 'MERGE_BIB_RECORDS',
759      '' );
760 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 281, 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
761      '' );
762 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 282, 'CREATE_ACQ_FUNDING_SOURCE',
763      '' );
764 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 283, 'CREATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
765      '' );
766 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 284, 'CREATE_AUTHORITY_IMPORT_QUEUE',
767      '' );
768 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 285, 'CREATE_AUTHORITY_RECORD_NOTE',
769      '' );
770 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 286, 'CREATE_BIB_IMPORT_FIELD_DEF',
771      '' );
772 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 287, 'CREATE_BIB_IMPORT_QUEUE',
773      '' );
774 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 288, 'CREATE_LOCALE',
775      '' );
776 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 289, 'CREATE_MARC_CODE',
777      '' );
778 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 290, 'CREATE_TRANSLATION',
779      '' );
780 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 291, 'DELETE_ACQ_FUNDING_SOURCE',
781      '' );
782 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 292, 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
783      '' );
784 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 293, 'DELETE_AUTHORITY_IMPORT_QUEUE',
785      '' );
786 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 294, 'DELETE_AUTHORITY_RECORD_NOTE',
787      '' );
788 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 295, 'DELETE_BIB_IMPORT_IMPORT_FIELD_DEF',
789      '' );
790 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 296, 'DELETE_BIB_IMPORT_QUEUE',
791      '' );
792 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 297, 'DELETE_LOCALE',
793      '' );
794 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 298, 'DELETE_MARC_CODE',
795      '' );
796 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 299, 'DELETE_TRANSLATION',
797      '' );
798 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 300, 'UPDATE_ACQ_FUNDING_SOURCE',
799      '' );
800 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 301, 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
801      '' );
802 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 302, 'UPDATE_AUTHORITY_IMPORT_QUEUE',
803      '' );
804 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 303, 'UPDATE_AUTHORITY_RECORD_NOTE',
805      '' );
806 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 304, 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
807      '' );
808 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 305, 'UPDATE_BIB_IMPORT_QUEUE',
809      '' );
810 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 306, 'UPDATE_LOCALE',
811      '' );
812 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 307, 'UPDATE_MARC_CODE',
813      '' );
814 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 308, 'UPDATE_TRANSLATION',
815      '' );
816 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 309, 'VIEW_ACQ_FUNDING_SOURCE',
817      '' );
818 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 310, 'VIEW_AUTHORITY_RECORD_NOTES',
819      '' );
820 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 311, 'CREATE_IMPORT_ITEM',
821      '' );
822 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 312, 'CREATE_IMPORT_ITEM_ATTR_DEF',
823      '' );
824 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 313, 'CREATE_IMPORT_TRASH_FIELD',
825      '' );
826 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 314, 'DELETE_IMPORT_ITEM',
827      '' );
828 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 315, 'DELETE_IMPORT_ITEM_ATTR_DEF',
829      '' );
830 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 316, 'DELETE_IMPORT_TRASH_FIELD',
831      '' );
832 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 317, 'UPDATE_IMPORT_ITEM',
833      '' );
834 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 318, 'UPDATE_IMPORT_ITEM_ATTR_DEF',
835      '' );
836 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 319, 'UPDATE_IMPORT_TRASH_FIELD',
837      '' );
838 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 320, 'UPDATE_ORG_UNIT_SETTING_ALL',
839      '' );
840 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 321, 'UPDATE_ORG_UNIT_SETTING.circ.lost_materials_processing_fee',
841      '' );
842 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 322, 'UPDATE_ORG_UNIT_SETTING.cat.default_item_price',
843      '' );
844 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 323, 'UPDATE_ORG_UNIT_SETTING.auth.opac_timeout',
845      '' );
846 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 324, 'UPDATE_ORG_UNIT_SETTING.auth.staff_timeout',
847      '' );
848 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 325, 'UPDATE_ORG_UNIT_SETTING.org.bounced_emails',
849      '' );
850 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 326, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_alert_interval',
851      '' );
852 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 327, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_interval',
853      '' );
854 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 328, 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow',
855      '' );
856 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 329, 'UPDATE_ORG_UNIT_SETTING.circ.void_overdue_on_lost',
857      '' );
858 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 330, 'UPDATE_ORG_UNIT_SETTING.circ.hold_stalling.soft',
859      '' );
860 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 331, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.hard',
861      '' );
862 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 332, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.soft',
863      '' );
864 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 333, 'UPDATE_ORG_UNIT_SETTING.opac.barcode_regex',
865      '' );
866 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 334, 'UPDATE_ORG_UNIT_SETTING.global.password_regex',
867      '' );
868 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 335, 'UPDATE_ORG_UNIT_SETTING.circ.item_checkout_history.max',
869      '' );
870 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 336, 'UPDATE_ORG_UNIT_SETTING.circ.reshelving_complete.interval',
871      '' );
872 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 337, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.patron_login_timeout',
873      '' );
874 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 338, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.alert_on_checkout_event',
875      '' );
876 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 339, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.require_patron_password',
877      '' );
878 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 340, 'UPDATE_ORG_UNIT_SETTING.global.juvenile_age_threshold',
879      '' );
880 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 341, 'UPDATE_ORG_UNIT_SETTING.cat.bib.keep_on_empty',
881      '' );
882 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 342, 'UPDATE_ORG_UNIT_SETTING.cat.bib.alert_on_empty',
883      '' );
884 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 343, 'UPDATE_ORG_UNIT_SETTING.patron.password.use_phone',
885      '' );
886 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 344, 'HOLD_ITEM_CHECKED_OUT.override',
887      'Allows a user to place a hold on an item that they already have checked out' );
888 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 345, 'ADMIN_ACQ_CANCEL_CAUSE',
889      'Allow a user to create/update/delete reasons for order cancellations' );
890 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 346, 'ACQ_XFER_MANUAL_DFUND_AMOUNT',
891      'Allow a user to transfer different amounts of money out of one fund and into another' );
892 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 347, 'OVERRIDE_HOLD_HAS_LOCAL_COPY',
893      'Allow a user to override the circ.holds.hold_has_copy_at.block setting' );
894 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 348, 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
895      'Allow a user to change the pickup and transit destination for a captured hold item already in transit' );
896 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 349, 'COPY_NEEDED_FOR_HOLD.override',
897      'Allow a user to force renewal of an item that could fulfill a hold request' );
898 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 350, 'MERGE_AUTH_RECORDS',
899      'Allow a user to merge authority records together' );
900 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 351, 'ALLOW_ALT_TCN',
901      'Allows staff to import a record using an alternate TCN to avoid conflicts' );
902 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 352, 'ADMIN_TRIGGER_EVENT_DEF',
903      'Allow a user to administer trigger event definitions' );
904 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 353, 'ADMIN_TRIGGER_CLEANUP',
905      'Allow a user to create, delete, and update trigger cleanup entries' );
906 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 354, 'CREATE_TRIGGER_CLEANUP',
907      'Allow a user to create trigger cleanup entries' );
908 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 355, 'DELETE_TRIGGER_CLEANUP',
909      'Allow a user to delete trigger cleanup entries' );
910 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 356, 'UPDATE_TRIGGER_CLEANUP',
911      'Allow a user to update trigger cleanup entries' );
912 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 357, 'CREATE_TRIGGER_EVENT_DEF',
913      'Allow a user to create trigger event definitions' );
914 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 358, 'DELETE_TRIGGER_EVENT_DEF',
915      'Allow a user to delete trigger event definitions' );
916 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 359, 'UPDATE_TRIGGER_EVENT_DEF',
917      'Allow a user to update trigger event definitions' );
918 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 360, 'VIEW_TRIGGER_EVENT_DEF',
919      'Allow a user to view trigger event definitions' );
920 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 361, 'ADMIN_TRIGGER_HOOK',
921      'Allow a user to create, update, and delete trigger hooks' );
922 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 362, 'CREATE_TRIGGER_HOOK',
923      'Allow a user to create trigger hooks' );
924 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 363, 'DELETE_TRIGGER_HOOK',
925      'Allow a user to delete trigger hooks' );
926 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 364, 'UPDATE_TRIGGER_HOOK',
927      'Allow a user to update trigger hooks' );
928 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 365, 'ADMIN_TRIGGER_REACTOR',
929      'Allow a user to create, update, and delete trigger reactors' );
930 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 366, 'CREATE_TRIGGER_REACTOR',
931      'Allow a user to create trigger reactors' );
932 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 367, 'DELETE_TRIGGER_REACTOR',
933      'Allow a user to delete trigger reactors' );
934 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 368, 'UPDATE_TRIGGER_REACTOR',
935      'Allow a user to update trigger reactors' );
936 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 369, 'ADMIN_TRIGGER_TEMPLATE_OUTPUT',
937      'Allow a user to delete trigger template output' );
938 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 370, 'DELETE_TRIGGER_TEMPLATE_OUTPUT',
939      'Allow a user to delete trigger template output' );
940 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 371, 'ADMIN_TRIGGER_VALIDATOR',
941      'Allow a user to create, update, and delete trigger validators' );
942 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 372, 'CREATE_TRIGGER_VALIDATOR',
943      'Allow a user to create trigger validators' );
944 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 373, 'DELETE_TRIGGER_VALIDATOR',
945      'Allow a user to delete trigger validators' );
946 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 374, 'UPDATE_TRIGGER_VALIDATOR',
947      'Allow a user to update trigger validators' );
948 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 376, 'ADMIN_BOOKING_RESOURCE',
949      'Enables the user to create/update/delete booking resources' );
950 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 377, 'ADMIN_BOOKING_RESOURCE_TYPE',
951      'Enables the user to create/update/delete booking resource types' );
952 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 378, 'ADMIN_BOOKING_RESOURCE_ATTR',
953      'Enables the user to create/update/delete booking resource attributes' );
954 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 379, 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
955      'Enables the user to create/update/delete booking resource attribute maps' );
956 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 380, 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
957      'Enables the user to create/update/delete booking resource attribute values' );
958 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 381, 'ADMIN_BOOKING_RESERVATION',
959      'Enables the user to create/update/delete booking reservations' );
960 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 382, 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
961      'Enables the user to create/update/delete booking reservation attribute value maps' );
962 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 383, 'RETRIEVE_RESERVATION_PULL_LIST',
963      'Allows a user to retrieve a booking reservation pull list' );
964 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 384, 'CAPTURE_RESERVATION',
965      'Allows a user to capture booking reservations' );
966 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 385, 'UPDATE_RECORD',
967      '' );
968 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 386, 'UPDATE_ORG_UNIT_SETTING.circ.block_renews_for_holds',
969      '' );
970 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 387, 'MERGE_USERS',
971      'Allows user records to be merged' );
972 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 388, 'ISSUANCE_HOLDS',
973      'Allow a user to place holds on serials issuances' );
974 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 389, 'VIEW_CREDIT_CARD_PROCESSING',
975      'View org unit settings related to credit card processing' );
976 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 390, 'ADMIN_CREDIT_CARD_PROCESSING',
977      'Update org unit settings related to credit card processing' );
978 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 391, 'ADMIN_SERIAL_CAPTION_PATTERN',
979         'Create/update/delete serial caption and pattern objects' );
980 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 392, 'ADMIN_SERIAL_SUBSCRIPTION',
981         'Create/update/delete serial subscription objects' );
982 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 393, 'ADMIN_SERIAL_DISTRIBUTION',
983         'Create/update/delete serial distribution objects' );
984 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 394, 'ADMIN_SERIAL_STREAM',
985         'Create/update/delete serial stream objects' );
986 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 395, 'RECEIVE_SERIAL',
987         'Receive serial items' );
988
989 -- Now for the permissions from the IDL.  We don't have descriptions for them.
990
991 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 396, 'ADMIN_ACQ_CLAIM' );
992 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 397, 'ADMIN_ACQ_CLAIM_EVENT_TYPE' );
993 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 398, 'ADMIN_ACQ_CLAIM_TYPE' );
994 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 399, 'ADMIN_ACQ_DISTRIB_FORMULA' );
995 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 400, 'ADMIN_ACQ_FISCAL_YEAR' );
996 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 401, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT' );
997 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 402, 'ADMIN_ACQ_FUND_TAG' );
998 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 403, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT' );
999 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 404, 'ADMIN_AGE_PROTECT_RULE' );
1000 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 405, 'ADMIN_ASSET_COPY_TEMPLATE' );
1001 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 406, 'ADMIN_BOOKING_RESERVATION_ATTR_MAP' );
1002 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 407, 'ADMIN_CIRC_MATRIX_MATCHPOINT' );
1003 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 408, 'ADMIN_CIRC_MOD' );
1004 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 409, 'ADMIN_CLAIM_POLICY' );
1005 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 410, 'ADMIN_CONFIG_REMOTE_ACCOUNT' );
1006 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 411, 'ADMIN_FIELD_DOC' );
1007 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 412, 'ADMIN_GLOBAL_FLAG' );
1008 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 413, 'ADMIN_GROUP_PENALTY_THRESHOLD' );
1009 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 414, 'ADMIN_HOLD_CANCEL_CAUSE' );
1010 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 415, 'ADMIN_HOLD_MATRIX_MATCHPOINT' );
1011 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 416, 'ADMIN_IDENT_TYPE' );
1012 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 417, 'ADMIN_IMPORT_ITEM_ATTR_DEF' );
1013 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 418, 'ADMIN_INDEX_NORMALIZER' );
1014 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 419, 'ADMIN_INVOICE' );
1015 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 420, 'ADMIN_INVOICE_METHOD' );
1016 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 421, 'ADMIN_INVOICE_PAYMENT_METHOD' );
1017 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 422, 'ADMIN_LINEITEM_MARC_ATTR_DEF' );
1018 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 423, 'ADMIN_MARC_CODE' );
1019 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 424, 'ADMIN_MAX_FINE_RULE' );
1020 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 425, 'ADMIN_MERGE_PROFILE' );
1021 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 426, 'ADMIN_ORG_UNIT_SETTING_TYPE' );
1022 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 427, 'ADMIN_RECURRING_FINE_RULE' );
1023 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 428, 'ADMIN_STANDING_PENALTY' );
1024 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 429, 'ADMIN_SURVEY' );
1025 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 430, 'ADMIN_USER_REQUEST_TYPE' );
1026 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 431, 'ADMIN_USER_SETTING_GROUP' );
1027 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 432, 'ADMIN_USER_SETTING_TYPE' );
1028 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 433, 'ADMIN_Z3950_SOURCE' );
1029 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 434, 'CREATE_BIB_BTYPE' );
1030 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 435, 'CREATE_BIBLIO_FINGERPRINT' );
1031 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 436, 'CREATE_BIB_SOURCE' );
1032 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 437, 'CREATE_BILLING_TYPE' );
1033 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 438, 'CREATE_CN_BTYPE' );
1034 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 439, 'CREATE_COPY_BTYPE' );
1035 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 440, 'CREATE_INVOICE' );
1036 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 441, 'CREATE_INVOICE_ITEM_TYPE' );
1037 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 442, 'CREATE_INVOICE_METHOD' );
1038 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 443, 'CREATE_MERGE_PROFILE' );
1039 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 444, 'CREATE_METABIB_CLASS' );
1040 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 445, 'CREATE_METABIB_SEARCH_ALIAS' );
1041 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 446, 'CREATE_USER_BTYPE' );
1042 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 447, 'DELETE_BIB_BTYPE' );
1043 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 448, 'DELETE_BIBLIO_FINGERPRINT' );
1044 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 449, 'DELETE_BIB_SOURCE' );
1045 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 450, 'DELETE_BILLING_TYPE' );
1046 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 451, 'DELETE_CN_BTYPE' );
1047 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 452, 'DELETE_COPY_BTYPE' );
1048 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 453, 'DELETE_INVOICE_ITEM_TYPE' );
1049 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 454, 'DELETE_INVOICE_METHOD' );
1050 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 455, 'DELETE_MERGE_PROFILE' );
1051 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 456, 'DELETE_METABIB_CLASS' );
1052 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 457, 'DELETE_METABIB_SEARCH_ALIAS' );
1053 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 458, 'DELETE_USER_BTYPE' );
1054 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 459, 'MANAGE_CLAIM' );
1055 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 460, 'UPDATE_BIB_BTYPE' );
1056 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 461, 'UPDATE_BIBLIO_FINGERPRINT' );
1057 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 462, 'UPDATE_BIB_SOURCE' );
1058 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 463, 'UPDATE_BILLING_TYPE' );
1059 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 464, 'UPDATE_CN_BTYPE' );
1060 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 465, 'UPDATE_COPY_BTYPE' );
1061 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 466, 'UPDATE_INVOICE_ITEM_TYPE' );
1062 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 467, 'UPDATE_INVOICE_METHOD' );
1063 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 468, 'UPDATE_MERGE_PROFILE' );
1064 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 469, 'UPDATE_METABIB_CLASS' );
1065 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 470, 'UPDATE_METABIB_SEARCH_ALIAS' );
1066 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 471, 'UPDATE_USER_BTYPE' );
1067 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 472, 'user_request.create' );
1068 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 473, 'user_request.delete' );
1069 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 474, 'user_request.update' );
1070 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 475, 'user_request.view' );
1071 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 476, 'VIEW_ACQ_FUND_ALLOCATION_PERCENT' );
1072 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 477, 'VIEW_CIRC_MATRIX_MATCHPOINT' );
1073 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 478, 'VIEW_CLAIM' );
1074 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 479, 'VIEW_GROUP_PENALTY_THRESHOLD' );
1075 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 480, 'VIEW_HOLD_MATRIX_MATCHPOINT' );
1076 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 481, 'VIEW_INVOICE' );
1077 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 482, 'VIEW_MERGE_PROFILE' );
1078 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 483, 'VIEW_SERIAL_SUBSCRIPTION' );
1079 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 484, 'VIEW_STANDING_PENALTY' );
1080
1081 -- For every permission in the temp_perm table that has a matching
1082 -- permission in the real table: record the original id.
1083
1084 UPDATE permission.temp_perm AS tp
1085 SET old_id =
1086         (
1087                 SELECT id
1088                 FROM permission.perm_list AS ppl
1089                 WHERE ppl.code = tp.code
1090         )
1091 WHERE code IN ( SELECT code FROM permission.perm_list );
1092
1093 -- Start juggling ids.
1094
1095 -- If any permissions have negative ids (with the special exception of -1),
1096 -- we need to move them into the positive range in order to avoid duplicate
1097 -- key problems (since we are going to use the negative range as a temporary
1098 -- staging area).
1099
1100 -- First, move any predefined permissions that have negative ids (again with
1101 -- the special exception of -1).  Temporarily give them positive ids based on
1102 -- the sequence.
1103
1104 UPDATE permission.perm_list
1105 SET id = NEXTVAL('permission.perm_list_id_seq'::regclass)
1106 WHERE id < -1
1107   AND code IN (SELECT code FROM permission.temp_perm);
1108
1109 -- Identify any non-predefined permissions whose ids are either negative
1110 -- or within the range (0-1000) reserved for predefined permissions.
1111 -- Assign them ids above 1000, based on the sequence.  Record the new
1112 -- ids in the temp_perm table.
1113
1114 INSERT INTO permission.temp_perm ( id, code, description, old_id, predefined )
1115 (
1116         SELECT NEXTVAL('permission.perm_list_id_seq'::regclass),
1117                 code, description, id, false
1118         FROM permission.perm_list
1119         WHERE  ( id < -1 OR id BETWEEN 0 AND 1000 )
1120         AND code NOT IN (SELECT code FROM permission.temp_perm)
1121 );
1122
1123 -- Now update the ids of those non-predefined permissions, using the
1124 -- values assigned in the previous step.
1125
1126 UPDATE permission.perm_list AS ppl
1127 SET id = (
1128                 SELECT id
1129                 FROM permission.temp_perm AS tp
1130                 WHERE tp.code = ppl.code
1131         )
1132 WHERE id IN ( SELECT old_id FROM permission.temp_perm WHERE NOT predefined );
1133
1134 -- Now the negative ids have been eliminated, except for -1.  Move all the
1135 -- predefined permissions temporarily into the negative range.
1136
1137 UPDATE permission.perm_list
1138 SET id = -1 - id
1139 WHERE id <> -1
1140 AND code IN ( SELECT code from permission.temp_perm WHERE predefined );
1141
1142 -- Apply the final ids to the existing predefined permissions.
1143
1144 UPDATE permission.perm_list AS ppl
1145 SET id =
1146         (
1147                 SELECT id
1148                 FROM permission.temp_perm AS tp
1149                 WHERE tp.code = ppl.code
1150         )
1151 WHERE
1152         id <> -1
1153         AND ppl.code IN
1154         (
1155                 SELECT code from permission.temp_perm
1156                 WHERE predefined
1157                 AND old_id IS NOT NULL
1158         );
1159
1160 -- If there are any predefined permissions that don't exist yet in
1161 -- permission.perm_list, insert them now.
1162
1163 INSERT INTO permission.perm_list ( id, code, description )
1164 (
1165         SELECT id, code, description
1166         FROM permission.temp_perm
1167         WHERE old_id IS NULL
1168 );
1169
1170 -- Reset the sequence to the lowest feasible value.  This may or may not
1171 -- accomplish anything, but it will do no harm.
1172
1173 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, GREATEST( 
1174         (SELECT MAX(id) FROM permission.perm_list), 1000 ));
1175
1176 -- If any permission lacks a description, use the code as a description.
1177 -- It's better than nothing.
1178
1179 UPDATE permission.perm_list
1180 SET description = code
1181 WHERE description IS NULL
1182    OR description = '';
1183
1184 -- Thus endeth the Great Renumbering.
1185
1186 -- Having massaged the permissions, massage the way they are assigned, by inserting
1187 -- rows into permission.grp_perm_map.  Some of these permissions may have already
1188 -- been assigned, so we insert the rows only if they aren't already there.
1189
1190 -- for backwards compat, give everyone the permission
1191 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1192     SELECT 1, id, 0, false FROM permission.perm_list AS perm
1193         WHERE code = 'HOLD_ITEM_CHECKED_OUT.override'
1194                 AND NOT EXISTS (
1195                         SELECT 1
1196                         FROM permission.grp_perm_map AS map
1197                         WHERE
1198                                 grp = 1
1199                                 AND map.perm = perm.id
1200                 );
1201
1202 -- Add trigger administration permissions to the Local System Administrator group.
1203 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1204     SELECT 10, id, 1, false FROM permission.perm_list AS perm
1205     WHERE (
1206                 perm.code LIKE 'ADMIN_TRIGGER%'
1207         OR perm.code LIKE 'CREATE_TRIGGER%'
1208         OR perm.code LIKE 'DELETE_TRIGGER%'
1209         OR perm.code LIKE 'UPDATE_TRIGGER%'
1210         ) AND NOT EXISTS (
1211                 SELECT 1
1212                 FROM permission.grp_perm_map AS map
1213                 WHERE
1214                         grp = 10
1215                         AND map.perm = perm.id
1216         );
1217
1218 -- View trigger permissions are required at a consortial level for initial setup
1219 -- (as before, only if the row doesn't already exist)
1220 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1221     SELECT 10, id, 0, false FROM permission.perm_list AS perm
1222         WHERE code LIKE 'VIEW_TRIGGER%'
1223                 AND NOT EXISTS (
1224                         SELECT 1
1225                         FROM permission.grp_perm_map AS map
1226                         WHERE
1227                                 grp = 10
1228                                 AND map.perm = perm.id
1229                 );
1230
1231 -- Permission for merging auth records may already be defined,
1232 -- so add it only if it isn't there.
1233 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1234     SELECT 4, id, 1, false FROM permission.perm_list AS perm
1235         WHERE code = 'MERGE_AUTH_RECORDS'
1236                 AND NOT EXISTS (
1237                         SELECT 1
1238                         FROM permission.grp_perm_map AS map
1239                         WHERE
1240                                 grp = 4
1241                                 AND map.perm = perm.id
1242                 );
1243
1244 -- Create a reference table as parent to both
1245 -- config.org_unit_setting_type and config_usr_setting_type
1246
1247 CREATE TABLE config.settings_group (
1248     name    TEXT PRIMARY KEY,
1249     label   TEXT UNIQUE NOT NULL -- I18N
1250 );
1251
1252 -- org_unit setting types
1253 CREATE TABLE config.org_unit_setting_type (
1254     name            TEXT    PRIMARY KEY,
1255     label           TEXT    UNIQUE NOT NULL,
1256     grp             TEXT    REFERENCES config.settings_group (name),
1257     description     TEXT,
1258     datatype        TEXT    NOT NULL DEFAULT 'string',
1259     fm_class        TEXT,
1260     view_perm       INT,
1261     update_perm     INT,
1262     --
1263     -- define valid datatypes
1264     --
1265     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1266     ( 'bool', 'integer', 'float', 'currency', 'interval',
1267       'date', 'string', 'object', 'array', 'link' ) ),
1268     --
1269     -- fm_class is meaningful only for 'link' datatype
1270     --
1271     CONSTRAINT coust_no_empty_link CHECK
1272     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
1273       ( datatype <> 'link' AND fm_class IS NULL ) ),
1274         CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id)
1275                 ON UPDATE CASCADE
1276                 ON DELETE RESTRICT
1277                 DEFERRABLE INITIALLY DEFERRED,
1278         CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id)
1279                 ON UPDATE CASCADE
1280                 DEFERRABLE INITIALLY DEFERRED
1281 );
1282
1283 CREATE TABLE config.usr_setting_type (
1284
1285     name TEXT PRIMARY KEY,
1286     opac_visible BOOL NOT NULL DEFAULT FALSE,
1287     label TEXT UNIQUE NOT NULL,
1288     description TEXT,
1289     grp             TEXT    REFERENCES config.settings_group (name),
1290     datatype TEXT NOT NULL DEFAULT 'string',
1291     fm_class TEXT,
1292
1293     --
1294     -- define valid datatypes
1295     --
1296     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1297     ( 'bool', 'integer', 'float', 'currency', 'interval',
1298         'date', 'string', 'object', 'array', 'link' ) ),
1299
1300     --
1301     -- fm_class is meaningful only for 'link' datatype
1302     --
1303     CONSTRAINT coust_no_empty_link CHECK
1304     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
1305         ( datatype <> 'link' AND fm_class IS NULL ) )
1306
1307 );
1308
1309 --------------------------------------
1310 -- Seed data for org_unit_setting_type
1311 --------------------------------------
1312
1313 INSERT into config.org_unit_setting_type
1314 ( name, label, description, datatype ) VALUES
1315
1316 ( 'auth.opac_timeout',
1317   'OPAC Inactivity Timeout (in seconds)',
1318   null,
1319   'integer' ),
1320
1321 ( 'auth.staff_timeout',
1322   'Staff Login Inactivity Timeout (in seconds)',
1323   null,
1324   'integer' ),
1325
1326 ( 'circ.lost_materials_processing_fee',
1327   'Lost Materials Processing Fee',
1328   null,
1329   'currency' ),
1330
1331 ( 'cat.default_item_price',
1332   'Default Item Price',
1333   null,
1334   'currency' ),
1335
1336 ( 'org.bounced_emails',
1337   'Sending email address for patron notices',
1338   null,
1339   'string' ),
1340
1341 ( 'circ.hold_expire_alert_interval',
1342   'Holds: Expire Alert Interval',
1343   'Amount of time before a hold expires at which point the patron should be alerted',
1344   'interval' ),
1345
1346 ( 'circ.hold_expire_interval',
1347   'Holds: Expire Interval',
1348   'Amount of time after a hold is placed before the hold expires.  Example "100 days"',
1349   'interval' ),
1350
1351 ( 'credit.payments.allow',
1352   'Allow Credit Card Payments',
1353   'If enabled, patrons will be able to pay fines accrued at this location via credit card',
1354   'bool' ),
1355
1356 ( 'global.default_locale',
1357   'Global Default Locale',
1358   null,
1359   'string' ),
1360
1361 ( 'circ.void_overdue_on_lost',
1362   'Void overdue fines when items are marked lost',
1363   null,
1364   'bool' ),
1365
1366 ( 'circ.hold_stalling.soft',
1367   'Holds: Soft stalling interval',
1368   'How long to wait before allowing remote items to be opportunistically captured for a hold.  Example "5 days"',
1369   'interval' ),
1370
1371 ( 'circ.hold_stalling_hard',
1372   'Holds: Hard stalling interval',
1373   '',
1374   'interval' ),
1375
1376 ( 'circ.hold_boundary.hard',
1377   'Holds: Hard boundary',
1378   null,
1379   'integer' ),
1380
1381 ( 'circ.hold_boundary.soft',
1382   'Holds: Soft boundary',
1383   null,
1384   'integer' ),
1385
1386 ( 'opac.barcode_regex',
1387   'Patron barcode format',
1388   'Regular expression defining the patron barcode format',
1389   'string' ),
1390
1391 ( 'global.password_regex',
1392   'Password format',
1393   'Regular expression defining the password format',
1394   'string' ),
1395
1396 ( 'circ.item_checkout_history.max',
1397   'Maximum previous checkouts displayed',
1398   'This is the maximum number of previous circulations the staff client will display when investigating item details',
1399   'integer' ),
1400
1401 ( 'circ.reshelving_complete.interval',
1402   'Change reshelving status interval',
1403   'Amount of time to wait before changing an item from "reshelving" status to "available".  Examples: "1 day", "6 hours"',
1404   'interval' ),
1405
1406 ( 'circ.holds.default_estimated_wait_interval',
1407   'Holds: Default Estimated Wait',
1408   'When predicting the amount of time a patron will be waiting for a hold to be fulfilled, this is the default estimated length of time to assume an item will be checked out.',
1409   'interval' ),
1410
1411 ( 'circ.holds.min_estimated_wait_interval',
1412   'Holds: Minimum Estimated Wait',
1413   'When predicting the amount of time a patron will be waiting for a hold to be fulfilled, this is the minimum estimated length of time to assume an item will be checked out.',
1414   'interval' ),
1415
1416 ( 'circ.selfcheck.patron_login_timeout',
1417   'Selfcheck: Patron Login Timeout (in seconds)',
1418   'Number of seconds of inactivity before the patron is logged out of the selfcheck interface',
1419   'integer' ),
1420
1421 ( 'circ.selfcheck.alert.popup',
1422   'Selfcheck: Pop-up alert for errors',
1423   'If true, checkout/renewal errors will cause a pop-up window in addition to the on-screen message',
1424   'bool' ),
1425
1426 ( 'circ.selfcheck.require_patron_password',
1427   'Selfcheck: Require patron password',
1428   'If true, patrons will be required to enter their password in addition to their username/barcode to log into the selfcheck interface',
1429   'bool' ),
1430
1431 ( 'global.juvenile_age_threshold',
1432   'Juvenile Age Threshold',
1433   'The age at which a user is no long considered a juvenile.  For example, "18 years".',
1434   'interval' ),
1435
1436 ( 'cat.bib.keep_on_empty',
1437   'Retain empty bib records',
1438   'Retain a bib record even when all attached copies are deleted',
1439   'bool' ),
1440
1441 ( 'cat.bib.alert_on_empty',
1442   'Alert on empty bib records',
1443   'Alert staff when the last copy for a record is being deleted',
1444   'bool' ),
1445
1446 ( 'patron.password.use_phone',
1447   'Patron: password from phone #',
1448   'Use the last 4 digits of the patrons phone number as the default password when creating new users',
1449   'bool' ),
1450
1451 ( 'circ.charge_on_damaged',
1452   'Charge item price when marked damaged',
1453   'Charge item price when marked damaged',
1454   'bool' ),
1455
1456 ( 'circ.charge_lost_on_zero',
1457   'Charge lost on zero',
1458   '',
1459   'bool' ),
1460
1461 ( 'circ.damaged_item_processing_fee',
1462   'Charge processing fee for damaged items',
1463   'Charge processing fee for damaged items',
1464   'currency' ),
1465
1466 ( 'circ.void_lost_on_checkin',
1467   'Circ: Void lost item billing when returned',
1468   'Void lost item billing when returned',
1469   'bool' ),
1470
1471 ( 'circ.max_accept_return_of_lost',
1472   'Circ: Void lost max interval',
1473   'Items that have been lost this long will not result in voided billings when returned.  E.g. ''6 months''',
1474   'interval' ),
1475
1476 ( 'circ.void_lost_proc_fee_on_checkin',
1477   'Circ: Void processing fee on lost item return',
1478   'Void processing fee when lost item returned',
1479   'bool' ),
1480
1481 ( 'circ.restore_overdue_on_lost_return',
1482   'Circ: Restore overdues on lost item return',
1483   'Restore overdue fines on lost item return',
1484   'bool' ),
1485
1486 ( 'circ.lost_immediately_available',
1487   'Circ: Lost items usable on checkin',
1488   'Lost items are usable on checkin instead of going ''home'' first',
1489   'bool' ),
1490
1491 ( 'circ.holds_fifo',
1492   'Holds: FIFO',
1493   'Force holds to a more strict First-In, First-Out capture',
1494   'bool' ),
1495
1496 ( 'opac.allow_pending_address',
1497   'OPAC: Allow pending addresses',
1498   'If enabled, patrons can create and edit existing addresses.  Addresses are kept in a pending state until staff approves the changes',
1499   'bool' ),
1500
1501 ( 'ui.circ.show_billing_tab_on_bills',
1502   'Show billing tab first when bills are present',
1503   'If enabled and a patron has outstanding bills and the alert page is not required, show the billing tab by default, instead of the checkout tab, when a patron is loaded',
1504   'bool' ),
1505
1506 ( 'ui.general.idle_timeout',
1507     'GUI: Idle timeout',
1508     'If you want staff client windows to be minimized after a certain amount of system idle time, set this to the number of seconds of idle time that you want to allow before minimizing (requires staff client restart).',
1509     'integer' ),
1510
1511 ( 'ui.circ.in_house_use.entry_cap',
1512   'GUI: Record In-House Use: Maximum # of uses allowed per entry.',
1513   'The # of uses entry in the Record In-House Use interface may not exceed the value of this setting.',
1514   'integer' ),
1515
1516 ( 'ui.circ.in_house_use.entry_warn',
1517   'GUI: Record In-House Use: # of uses threshold for Are You Sure? dialog.',
1518   'In the Record In-House Use interface, a submission attempt will warn if the # of uses field exceeds the value of this setting.',
1519   'integer' ),
1520
1521 ( 'acq.default_circ_modifier',
1522   'Default circulation modifier',
1523   null,
1524   'string' ),
1525
1526 ( 'acq.tmp_barcode_prefix',
1527   'Temporary barcode prefix',
1528   null,
1529   'string' ),
1530
1531 ( 'acq.tmp_callnumber_prefix',
1532   'Temporary call number prefix',
1533   null,
1534   'string' ),
1535
1536 ( 'ui.circ.patron_summary.horizontal',
1537   'Patron circulation summary is horizontal',
1538   null,
1539   'bool' ),
1540
1541 ( 'ui.staff.require_initials',
1542   oils_i18n_gettext('ui.staff.require_initials', 'GUI: Require staff initials for entry/edit of item/patron/penalty notes/messages.', 'coust', 'label'),
1543   oils_i18n_gettext('ui.staff.require_initials', 'Appends staff initials and edit date into note content.', 'coust', 'description'),
1544   'bool' ),
1545
1546 ( 'ui.general.button_bar',
1547   'Button bar',
1548   null,
1549   'bool' ),
1550
1551 ( 'circ.hold_shelf_status_delay',
1552   'Hold Shelf Status Delay',
1553   'The purpose is to provide an interval of time after an item goes into the on-holds-shelf status before it appears to patrons that it is actually on the holds shelf.  This gives staff time to process the item before it shows as ready-for-pickup.',
1554   'interval' ),
1555
1556 ( 'circ.patron_invalid_address_apply_penalty',
1557   'Invalid patron address penalty',
1558   'When set, if a patron address is set to invalid, a penalty is applied.',
1559   'bool' ),
1560
1561 ( 'circ.checkout_fills_related_hold',
1562   'Checkout Fills Related Hold',
1563   'When a patron checks out an item and they have no holds that directly target the item, the system will attempt to find a hold for the patron that could be fulfilled by the checked out item and fulfills it',
1564   'bool'),
1565
1566 ( 'circ.selfcheck.auto_override_checkout_events',
1567   'Selfcheck override events list',
1568   'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction',
1569   'array' ),
1570
1571 ( 'circ.staff_client.do_not_auto_attempt_print',
1572   'Disable Automatic Print Attempt Type List',
1573   'Disable automatic print attempts from staff client interfaces for the receipt types in this list.  Possible values: "Checkout", "Bill Pay", "Hold Slip", "Transit Slip", and "Hold/Transit Slip".  This is different from the Auto-Print checkbox in the pertinent interfaces in that it disables automatic print attempts altogether, rather than encouraging silent printing by suppressing the print dialog.  The Auto-Print checkbox in these interfaces have no effect on the behavior for this setting.  In the case of the Hold, Transit, and Hold/Transit slips, this also suppresses the alert dialogs that precede the print dialog (the ones that offer Print and Do Not Print as options).',
1574   'array' ),
1575
1576 ( 'ui.patron.default_inet_access_level',
1577   'Default level of patrons'' internet access',
1578   null,
1579   'integer' ),
1580
1581 ( 'circ.max_patron_claim_return_count',
1582     'Max Patron Claims Returned Count',
1583     'When this count is exceeded, a staff override is required to mark the item as claims returned',
1584     'integer' ),
1585
1586 ( 'circ.obscure_dob',
1587     'Obscure the Date of Birth field',
1588     'When true, the Date of Birth column in patron lists will default to Not Visible, and in the Patron Summary sidebar the value will display as <Hidden> unless the field label is clicked.',
1589     'bool' ),
1590
1591 ( 'circ.auto_hide_patron_summary',
1592     'GUI: Toggle off the patron summary sidebar after first view.',
1593     'When true, the patron summary sidebar will collapse after a new patron sub-interface is selected.',
1594     'bool' ),
1595
1596 ( 'credit.processor.default',
1597     'Credit card processing: Name default credit processor',
1598     'This can be "AuthorizeNet", "PayPal" (for the Website Payment Pro API), or "PayflowPro".',
1599     'string' ),
1600
1601 ( 'credit.processor.authorizenet.enabled',
1602     'Credit card processing: AuthorizeNet enabled',
1603     '',
1604     'bool' ),
1605
1606 ( 'credit.processor.authorizenet.login',
1607     'Credit card processing: AuthorizeNet login',
1608     '',
1609     'string' ),
1610
1611 ( 'credit.processor.authorizenet.password',
1612     'Credit card processing: AuthorizeNet password',
1613     '',
1614     'string' ),
1615
1616 ( 'credit.processor.authorizenet.server',
1617     'Credit card processing: AuthorizeNet server',
1618     'Required if using a developer/test account with AuthorizeNet',
1619     'string' ),
1620
1621 ( 'credit.processor.authorizenet.testmode',
1622     'Credit card processing: AuthorizeNet test mode',
1623     '',
1624     'bool' ),
1625
1626 ( 'credit.processor.paypal.enabled',
1627     'Credit card processing: PayPal enabled',
1628     '',
1629     'bool' ),
1630 ( 'credit.processor.paypal.login',
1631     'Credit card processing: PayPal login',
1632     '',
1633     'string' ),
1634 ( 'credit.processor.paypal.password',
1635     'Credit card processing: PayPal password',
1636     '',
1637     'string' ),
1638 ( 'credit.processor.paypal.signature',
1639     'Credit card processing: PayPal signature',
1640     '',
1641     'string' ),
1642 ( 'credit.processor.paypal.testmode',
1643     'Credit card processing: PayPal test mode',
1644     '',
1645     'bool' ),
1646
1647 ( 'ui.admin.work_log.max_entries',
1648     oils_i18n_gettext('ui.admin.work_log.max_entries', 'GUI: Work Log: Maximum Actions Logged', 'coust', 'label'),
1649     oils_i18n_gettext('ui.admin.work_log.max_entries', 'Maximum entries for "Most Recent Staff Actions" section of the Work Log interface.', 'coust', 'description'),
1650   'interval' ),
1651
1652 ( 'ui.admin.patron_log.max_entries',
1653     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'GUI: Work Log: Maximum Patrons Logged', 'coust', 'label'),
1654     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'Maximum entries for "Most Recently Affected Patrons..." section of the Work Log interface.', 'coust', 'description'),
1655   'interval' ),
1656
1657 ( 'lib.courier_code',
1658     oils_i18n_gettext('lib.courier_code', 'Courier Code', 'coust', 'label'),
1659     oils_i18n_gettext('lib.courier_code', 'Courier Code for the library.  Available in transit slip templates as the %courier_code% macro.', 'coust', 'description'),
1660     'string'),
1661
1662 ( 'circ.block_renews_for_holds',
1663     oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
1664     oils_i18n_gettext('circ.block_renews_for_holds', 'When an item could fulfill a hold, do not allow the current patron to renew', 'coust', 'description'),
1665     'bool' ),
1666
1667 ( 'circ.password_reset_request_per_user_limit',
1668     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
1669     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'When a user has more than this number of concurrently active self-serve password reset requests for their account, prevent the user from creating any new self-serve password reset requests until the number of active requests for the user drops back below this number.', 'coust', 'description'),
1670     'string'),
1671
1672 ( 'circ.password_reset_request_time_to_live',
1673     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
1674     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Length of time (in seconds) a self-serve password reset request should remain active.', 'coust', 'description'),
1675     'string'),
1676
1677 ( 'circ.password_reset_request_throttle',
1678     oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
1679     oils_i18n_gettext('circ.password_reset_request_throttle', 'Prevent the creation of new self-serve password reset requests until the number of active requests drops back below this number.', 'coust', 'description'),
1680     'string')
1681 ;
1682
1683 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1684         'ui.circ.suppress_checkin_popups',
1685         oils_i18n_gettext(
1686             'ui.circ.suppress_checkin_popups', 
1687             'Circ: Suppress popup-dialogs during check-in.', 
1688             'coust', 
1689             'label'),
1690         oils_i18n_gettext(
1691             'ui.circ.suppress_checkin_popups', 
1692             'Circ: Suppress popup-dialogs during check-in.', 
1693             'coust', 
1694             'description'),
1695         'bool'
1696 );
1697
1698 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1699         'format.date',
1700         oils_i18n_gettext(
1701             'format.date',
1702             'GUI: Format Dates with this pattern.', 
1703             'coust', 
1704             'label'),
1705         oils_i18n_gettext(
1706             'format.date',
1707             'GUI: Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010")', 
1708             'coust', 
1709             'description'),
1710         'string'
1711 ), (
1712         'format.time',
1713         oils_i18n_gettext(
1714             'format.time',
1715             'GUI: Format Times with this pattern.', 
1716             'coust', 
1717             'label'),
1718         oils_i18n_gettext(
1719             'format.time',
1720             'GUI: Format Times with this pattern (examples: "h:m:s.SSS a z" for "2:07:20.666 PM Eastern Daylight Time", "HH:mm" for "14:07")', 
1721             'coust', 
1722             'description'),
1723         'string'
1724 );
1725
1726 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1727         'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1728         oils_i18n_gettext(
1729             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1730             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1731             'coust', 
1732             'label'),
1733         oils_i18n_gettext(
1734             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1735             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1736             'coust', 
1737             'description'),
1738         'bool'
1739 );
1740
1741 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1742         'url.remote_column_settings',
1743         oils_i18n_gettext(
1744             'url.remote_column_settings',
1745             'GUI: URL for remote directory containing list column settings.', 
1746             'coust', 
1747             'label'),
1748         oils_i18n_gettext(
1749             'url.remote_column_settings',
1750             'GUI: URL for remote directory containing list column settings.  The format and naming convention for the files found in this directory match those in the local settings directory for a given workstation.  An administrator could create the desired settings locally and then copy all the tree_columns_for_* files to the remote directory.', 
1751             'coust', 
1752             'description'),
1753         'string'
1754 );
1755
1756 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1757         'gui.disable_local_save_columns',
1758         oils_i18n_gettext(
1759             'gui.disable_local_save_columns',
1760             'GUI: Disable the ability to save list column configurations locally.', 
1761             'coust', 
1762             'label'),
1763         oils_i18n_gettext(
1764             'gui.disable_local_save_columns',
1765             'GUI: Disable the ability to save list column configurations locally.  If set, columns may still be manipulated, however, the changes do not persist.  Also, existing local configurations are ignored if this setting is true.', 
1766             'coust', 
1767             'description'),
1768         'bool'
1769 );
1770
1771 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1772         'circ.password_reset_request_requires_matching_email',
1773         oils_i18n_gettext(
1774             'circ.password_reset_request_requires_matching_email',
1775             'Circulation: Require matching email address for password reset requests', 
1776             'coust', 
1777             'label'),
1778         oils_i18n_gettext(
1779             'circ.password_reset_request_requires_matching_email',
1780             'Circulation: Require matching email address for password reset requests', 
1781             'coust', 
1782             'description'),
1783         'bool'
1784 );
1785
1786 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1787         'circ.holds.expired_patron_block',
1788         oils_i18n_gettext(
1789             'circ.holds.expired_patron_block',
1790             'Circulation: Block hold request if hold recipient privileges have expired', 
1791             'coust', 
1792             'label'),
1793         oils_i18n_gettext(
1794             'circ.holds.expired_patron_block',
1795             'Circulation: Block hold request if hold recipient privileges have expired', 
1796             'coust', 
1797             'description'),
1798         'bool'
1799 );
1800
1801 INSERT INTO config.org_unit_setting_type
1802     (name, label, description, datatype) VALUES (
1803         'circ.booking_reservation.default_elbow_room',
1804         oils_i18n_gettext(
1805             'circ.booking_reservation.default_elbow_room',
1806             'Booking: Elbow room',
1807             'coust',
1808             'label'
1809         ),
1810         oils_i18n_gettext(
1811             'circ.booking_reservation.default_elbow_room',
1812             'Elbow room specifies how far in the future you must make a reservation on an item if that item will have to transit to reach its pickup location.  It secondarily defines how soon a reservation on a given item must start before the check-in process will opportunistically capture it for the reservation shelf.',
1813             'coust',
1814             'label'
1815         ),
1816         'interval'
1817     );
1818
1819 -- Org_unit_setting_type(s) that need an fm_class:
1820 INSERT into config.org_unit_setting_type
1821 ( name, label, description, datatype, fm_class ) VALUES
1822 ( 'acq.default_copy_location',
1823   'Default copy location',
1824   null,
1825   'link',
1826   'acpl' );
1827
1828 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1829     'circ.holds.org_unit_target_weight',
1830     'Holds: Org Unit Target Weight',
1831     'Org Units can be organized into hold target groups based on a weight.  Potential copies from org units with the same weight are chosen at random.',
1832     'integer'
1833 );
1834
1835 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1836     'circ.holds.target_holds_by_org_unit_weight',
1837     'Holds: Use weight-based hold targeting',
1838     'Use library weight based hold targeting',
1839     'bool'
1840 );
1841
1842 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1843     'circ.holds.max_org_unit_target_loops',
1844     'Holds: Maximum library target attempts',
1845     'When this value is set and greater than 0, the system will only attempt to find a copy at each possible branch the configured number of times',
1846     'integer'
1847 );
1848
1849
1850 -- Org setting for overriding the circ lib of a precat copy
1851 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1852     'circ.pre_cat_copy_circ_lib',
1853     'Pre-cat Item Circ Lib',
1854     'Override the default circ lib of "here" with a pre-configured circ lib for pre-cat items.  The value should be the "shortname" (aka policy name) of the org unit',
1855     'string'
1856 );
1857
1858 -- Circ auto-renew interval setting
1859 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1860     'circ.checkout_auto_renew_age',
1861     'Checkout auto renew age',
1862     'When an item has been checked out for at least this amount of time, an attempt to check out the item to the patron that it is already checked out to will simply renew the circulation',
1863     'interval'
1864 );
1865
1866 -- Setting for behind the desk hold pickups
1867 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1868     'circ.holds.behind_desk_pickup_supported',
1869     'Holds: Behind Desk Pickup Supported',
1870     'If a branch supports both a public holds shelf and behind-the-desk pickups, set this value to true.  This gives the patron the option to enable behind-the-desk pickups for their holds',
1871     'bool'
1872 );
1873
1874 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1875         'acq.holds.allow_holds_from_purchase_request',
1876         oils_i18n_gettext(
1877             'acq.holds.allow_holds_from_purchase_request', 
1878             'Allows patrons to create automatic holds from purchase requests.', 
1879             'coust', 
1880             'label'),
1881         oils_i18n_gettext(
1882             'acq.holds.allow_holds_from_purchase_request', 
1883             'Allows patrons to create automatic holds from purchase requests.', 
1884             'coust', 
1885             'description'),
1886         'bool'
1887 );
1888
1889 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1890     'circ.holds.target_skip_me',
1891     'Skip For Hold Targeting',
1892     'When true, don''t target any copies at this org unit for holds',
1893     'bool'
1894 );
1895
1896 -- claims returned mark item missing 
1897 INSERT INTO
1898     config.org_unit_setting_type ( name, label, description, datatype )
1899     VALUES (
1900         'circ.claim_return.mark_missing',
1901         'Claim Return: Mark copy as missing', 
1902         'When a circ is marked as claims-returned, also mark the copy as missing',
1903         'bool'
1904     );
1905
1906 -- claims never checked out mark item missing 
1907 INSERT INTO
1908     config.org_unit_setting_type ( name, label, description, datatype )
1909     VALUES (
1910         'circ.claim_never_checked_out.mark_missing',
1911         'Claim Never Checked Out: Mark copy as missing', 
1912         'When a circ is marked as claims-never-checked-out, mark the copy as missing',
1913         'bool'
1914     );
1915
1916 -- mark damaged void overdue setting
1917 INSERT INTO
1918     config.org_unit_setting_type ( name, label, description, datatype )
1919     VALUES (
1920         'circ.damaged.void_ovedue',
1921         'Mark item damaged voids overdues',
1922         'When an item is marked damaged, overdue fines on the most recent circulation are voided.',
1923         'bool'
1924     );
1925
1926 -- hold cancel display limits
1927 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1928     VALUES (
1929         'circ.holds.canceled.display_count',
1930         'Holds: Canceled holds display count',
1931         'How many canceled holds to show in patron holds interfaces',
1932         'integer'
1933     );
1934
1935 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1936     VALUES (
1937         'circ.holds.canceled.display_age',
1938         'Holds: Canceled holds display age',
1939         'Show all canceled holds that were canceled within this amount of time',
1940         'interval'
1941     );
1942
1943 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1944     VALUES (
1945         'circ.holds.uncancel.reset_request_time',
1946         'Holds: Reset request time on un-cancel',
1947         'When a hold is uncanceled, reset the request time to push it to the end of the queue',
1948         'bool'
1949     );
1950
1951 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1952     VALUES (
1953         'circ.holds.default_shelf_expire_interval',
1954         'Default hold shelf expire interval',
1955         '',
1956         'interval'
1957 );
1958
1959 INSERT INTO config.org_unit_setting_type (name, label, description, datatype, fm_class)
1960     VALUES (
1961         'circ.claim_return.copy_status', 
1962         'Claim Return Copy Status', 
1963         'Claims returned copies are put into this status.  Default is to leave the copy in the Checked Out status',
1964         'link', 
1965         'ccs' 
1966     );
1967
1968 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
1969     VALUES ( 
1970         'circ.max_fine.cap_at_price',
1971         oils_i18n_gettext('circ.max_fine.cap_at_price', 'Circ: Cap Max Fine at Item Price', 'coust', 'label'),
1972         oils_i18n_gettext('circ.max_fine.cap_at_price', 'This prevents the system from charging more than the item price in overdue fines', 'coust', 'description'),
1973         'bool' 
1974     );
1975
1976 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) 
1977     VALUES ( 
1978         'circ.holds.clear_shelf.copy_status',
1979         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Holds: Clear shelf copy status', 'coust', 'label'),
1980         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Any copies that have not been put into reshelving, in-transit, or on-holds-shelf (for a new hold) during the clear shelf process will be put into this status.  This is basically a purgatory status for copies waiting to be pulled from the shelf and processed by hand', 'coust', 'description'),
1981         'link',
1982         'ccs'
1983     );
1984
1985 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1986     VALUES ( 
1987         'circ.selfcheck.workstation_required',
1988         oils_i18n_gettext('circ.selfcheck.workstation_required', 'Selfcheck: Workstation Required', 'coust', 'label'),
1989         oils_i18n_gettext('circ.selfcheck.workstation_required', 'All selfcheck stations must use a workstation', 'coust', 'description'),
1990         'bool'
1991     ), (
1992         'circ.selfcheck.patron_password_required',
1993         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Selfcheck: Require Patron Password', 'coust', 'label'),
1994         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Patron must log in with barcode and password at selfcheck station', 'coust', 'description'),
1995         'bool'
1996     );
1997
1998 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1999     VALUES ( 
2000         'circ.selfcheck.alert.sound',
2001         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Selfcheck: Audio Alerts', 'coust', 'label'),
2002         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Use audio alerts for selfcheck events', 'coust', 'description'),
2003         'bool'
2004     );
2005
2006 INSERT INTO
2007     config.org_unit_setting_type (name, label, description, datatype)
2008     VALUES (
2009         'notice.telephony.callfile_lines',
2010         'Telephony: Arbitrary line(s) to include in each notice callfile',
2011         $$
2012         This overrides lines from opensrf.xml.
2013         Line(s) must be valid for your target server and platform
2014         (e.g. Asterisk 1.4).
2015         $$,
2016         'string'
2017     );
2018
2019 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2020     VALUES ( 
2021         'circ.offline.username_allowed',
2022         oils_i18n_gettext('circ.offline.username_allowed', 'Offline: Patron Usernames Allowed', 'coust', 'label'),
2023         oils_i18n_gettext('circ.offline.username_allowed', 'During offline circulations, allow patrons to identify themselves with usernames in addition to barcode.  For this setting to work, a barcode format must also be defined', 'coust', 'description'),
2024         'bool'
2025     );
2026
2027 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2028 VALUES (
2029     'acq.fund.balance_limit.warn',
2030     oils_i18n_gettext('acq.fund.balance_limit.warn', 'Fund Spending Limit for Warning', 'coust', 'label'),
2031     oils_i18n_gettext('acq.fund.balance_limit.warn', 'When the amount remaining in the fund, including spent money and encumbrances, goes below this percentage, attempts to spend from the fund will result in a warning to the staff.', 'coust', 'descripton'),
2032     'integer'
2033 );
2034
2035 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2036 VALUES (
2037     'acq.fund.balance_limit.block',
2038     oils_i18n_gettext('acq.fund.balance_limit.block', 'Fund Spending Limit for Block', 'coust', 'label'),
2039     oils_i18n_gettext('acq.fund.balance_limit.block', 'When the amount remaining in the fund, including spent money and encumbrances, goes below this percentage, attempts to spend from the fund will be blocked.', 'coust', 'description'),
2040     'integer'
2041 );
2042
2043 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2044     VALUES (
2045         'circ.holds.hold_has_copy_at.alert',
2046         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'Holds: Has Local Copy Alert', 'coust', 'label'),
2047         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'If there is an available copy at the requesting library that could fulfill a hold during hold placement time, alert the patron', 'coust', 'description'),
2048         'bool'
2049     ),(
2050         'circ.holds.hold_has_copy_at.block',
2051         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'Holds: Has Local Copy Block', 'coust', 'label'),
2052         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'If there is an available copy at the requesting library that could fulfill a hold during hold placement time, do not allow the hold to be placed', 'coust', 'description'),
2053         'bool'
2054     );
2055
2056 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2057 VALUES (
2058     'auth.persistent_login_interval',
2059     oils_i18n_gettext('auth.persistent_login_interval', 'Persistent Login Duration', 'coust', 'label'),
2060     oils_i18n_gettext('auth.persistent_login_interval', 'How long a persistent login lasts.  E.g. ''2 weeks''', 'coust', 'description'),
2061     'interval'
2062 );
2063
2064 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2065         'cat.marc_control_number_identifier',
2066         oils_i18n_gettext(
2067             'cat.marc_control_number_identifier', 
2068             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2069             'coust', 
2070             'label'),
2071         oils_i18n_gettext(
2072             'cat.marc_control_number_identifier', 
2073             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2074             'coust', 
2075             'description'),
2076         'string'
2077 );
2078
2079 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) 
2080     VALUES (
2081         'circ.selfcheck.block_checkout_on_copy_status',
2082         oils_i18n_gettext(
2083             'circ.selfcheck.block_checkout_on_copy_status',
2084             'Selfcheck: Block copy checkout status',
2085             'coust',
2086             'label'
2087         ),
2088         oils_i18n_gettext(
2089             'circ.selfcheck.block_checkout_on_copy_status',
2090             'List of copy status IDs that will block checkout even if the generic COPY_NOT_AVAILABLE event is overridden',
2091             'coust',
2092             'description'
2093         ),
2094         'array'
2095     );
2096
2097 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2098 VALUES (
2099     'serial.prev_issuance_copy_location',
2100     oils_i18n_gettext(
2101         'serial.prev_issuance_copy_location',
2102         'Serials: Previous Issuance Copy Location',
2103         'coust',
2104         'label'
2105     ),
2106     oils_i18n_gettext(
2107         'serial.prev_issuance_copy_location',
2108         'When a serial issuance is received, copies (units) of the previous issuance will be automatically moved into the configured shelving location',
2109         'coust',
2110         'descripton'
2111         ),
2112     'link',
2113     'acpl'
2114 );
2115
2116 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2117 VALUES (
2118     'cat.default_classification_scheme',
2119     oils_i18n_gettext(
2120         'cat.default_classification_scheme',
2121         'Cataloging: Default Classification Scheme',
2122         'coust',
2123         'label'
2124     ),
2125     oils_i18n_gettext(
2126         'cat.default_classification_scheme',
2127         'Defines the default classification scheme for new call numbers: 1 = Generic; 2 = Dewey; 3 = LC',
2128         'coust',
2129         'descripton'
2130         ),
2131     'link',
2132     'acnc'
2133 );
2134
2135 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2136         'opac.org_unit_hiding.depth',
2137         oils_i18n_gettext(
2138             'opac.org_unit_hiding.depth',
2139             'OPAC: Org Unit Hiding Depth', 
2140             'coust', 
2141             'label'),
2142         oils_i18n_gettext(
2143             'opac.org_unit_hiding.depth',
2144             'This will hide certain org units in the public OPAC if the Original Location (url param "ol") for the OPAC inherits this setting.  This setting specifies an org unit depth, that together with the OPAC Original Location determines which section of the Org Hierarchy should be visible in the OPAC.  For example, a stock Evergreen installation will have a 3-tier hierarchy (Consortium/System/Branch), where System has a depth of 1 and Branch has a depth of 2.  If this setting contains a depth of 1 in such an installation, then every library in the System in which the Original Location belongs will be visible, and everything else will be hidden.  A depth of 0 will effectively make every org visible.  The embedded OPAC in the staff client ignores this setting.', 
2145             'coust', 
2146             'description'),
2147         'integer'
2148 );
2149
2150 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2151     VALUES ( 
2152         'circ.holds.clear_shelf.no_capture_holds',
2153         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'Holds: Bypass hold capture during clear shelf process', 'coust', 'label'),
2154         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'During the clear shelf process, avoid capturing new holds on cleared items.', 'coust', 'description'),
2155         'bool'
2156     );
2157
2158 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
2159     'circ.booking_reservation.stop_circ',
2160     'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
2161     'When true, items on booking reserve during the proposed checkout period will not be allowed to circulate unless overridden with the COPY_RESERVED.override permission.',
2162     'bool'
2163 );
2164
2165 ---------------------------------
2166 -- Seed data for usr_setting_type
2167 ----------------------------------
2168
2169 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2170     VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
2171
2172 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2173     VALUES ('opac.default_search_depth', TRUE, 'OPAC Search Depth', 'OPAC Search Depth', 'integer');
2174
2175 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2176     VALUES ('opac.default_search_location', TRUE, 'OPAC Search Location', 'OPAC Search Location', 'integer');
2177
2178 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2179     VALUES ('opac.hits_per_page', TRUE, 'Hits per Page', 'Hits per Page', 'string');
2180
2181 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2182     VALUES ('opac.hold_notify', TRUE, 'Hold Notification Format', 'Hold Notification Format', 'string');
2183
2184 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2185     VALUES ('staff_client.catalog.record_view.default', TRUE, 'Default Record View', 'Default Record View', 'string');
2186
2187 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2188     VALUES ('staff_client.copy_editor.templates', TRUE, 'Copy Editor Template', 'Copy Editor Template', 'object');
2189
2190 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2191     VALUES ('circ.holds_behind_desk', FALSE, 'Hold is behind Circ Desk', 'Hold is behind Circ Desk', 'bool');
2192
2193 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2194     VALUES (
2195         'history.circ.retention_age',
2196         TRUE,
2197         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','label'),
2198         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','description'),
2199         'interval'
2200     ),(
2201         'history.circ.retention_start',
2202         FALSE,
2203         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','label'),
2204         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','description'),
2205         'date'
2206     );
2207
2208 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2209     VALUES (
2210         'history.hold.retention_age',
2211         TRUE,
2212         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','label'),
2213         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','description'),
2214         'interval'
2215     ),(
2216         'history.hold.retention_start',
2217         TRUE,
2218         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','label'),
2219         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','description'),
2220         'interval'
2221     ),(
2222         'history.hold.retention_count',
2223         TRUE,
2224         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','label'),
2225         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','description'),
2226         'integer'
2227     );
2228
2229 INSERT INTO config.usr_setting_type (name, opac_visible, label, description, datatype)
2230     VALUES (
2231         'opac.default_sort',
2232         TRUE,
2233         oils_i18n_gettext(
2234             'opac.default_sort',
2235             'OPAC Default Search Sort',
2236             'cust',
2237             'label'
2238         ),
2239         oils_i18n_gettext(
2240             'opac.default_sort',
2241             'OPAC Default Search Sort',
2242             'cust',
2243             'description'
2244         ),
2245         'string'
2246     );
2247
2248 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2249         'circ.missing_pieces.copy_status',
2250         oils_i18n_gettext(
2251             'circ.missing_pieces.copy_status',
2252             'Circulation: Item Status for Missing Pieces',
2253             'coust',
2254             'label'),
2255         oils_i18n_gettext(
2256             'circ.missing_pieces.copy_status',
2257             'This is the Item Status to use for items that have been marked or scanned as having Missing Pieces.  In the absence of this setting, the Damaged status is used.',
2258             'coust',
2259             'description'),
2260         'link',
2261         'ccs'
2262 );
2263
2264 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2265         'circ.do_not_tally_claims_returned',
2266         oils_i18n_gettext(
2267             'circ.do_not_tally_claims_returned',
2268             'Circulation: Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.',
2269             'coust',
2270             'label'),
2271         oils_i18n_gettext(
2272             'circ.do_not_tally_claims_returned',
2273             'In the Patron Display interface, the number of total active circulations for a given patron is presented in the Summary sidebar and underneath the Items Out navigation button.  This setting will prevent Claims Returned circulations from counting toward these tallies.',
2274             'coust',
2275             'description'),
2276         'bool'
2277 );
2278
2279 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2280     VALUES
2281         ('cat.label.font.size',
2282             oils_i18n_gettext('cat.label.font.size',
2283                 'Cataloging: Spine and pocket label font size', 'coust', 'label'),
2284             oils_i18n_gettext('cat.label.font.size',
2285                 'Set the default font size for spine and pocket labels', 'coust', 'description'),
2286             'integer'
2287         )
2288         ,('cat.label.font.family',
2289             oils_i18n_gettext('cat.label.font.family',
2290                 'Cataloging: Spine and pocket label font family', 'coust', 'label'),
2291             oils_i18n_gettext('cat.label.font.family',
2292                 'Set the preferred font family for spine and pocket labels. You can specify a list of fonts, separated by commas, in order of preference; the system will use the first font it finds with a matching name. For example, "Arial, Helvetica, serif".',
2293                 'coust', 'description'),
2294             'string'
2295         )
2296         ,('cat.spine.line.width',
2297             oils_i18n_gettext('cat.spine.line.width',
2298                 'Cataloging: Spine label line width', 'coust', 'label'),
2299             oils_i18n_gettext('cat.spine.line.width',
2300                 'Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.',
2301                 'coust', 'description'),
2302             'integer'
2303         )
2304         ,('cat.spine.line.height',
2305             oils_i18n_gettext('cat.spine.line.height',
2306                 'Cataloging: Spine label maximum lines', 'coust', 'label'),
2307             oils_i18n_gettext('cat.spine.line.height',
2308                 'Set the default maximum number of lines for spine labels.',
2309                 'coust', 'description'),
2310             'integer'
2311         )
2312         ,('cat.spine.line.margin',
2313             oils_i18n_gettext('cat.spine.line.margin',
2314                 'Cataloging: Spine label left margin', 'coust', 'label'),
2315             oils_i18n_gettext('cat.spine.line.margin',
2316                 'Set the left margin for spine labels in number of characters.',
2317                 'coust', 'description'),
2318             'integer'
2319         )
2320 ;
2321
2322 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2323     VALUES
2324         ('cat.label.font.weight',
2325             oils_i18n_gettext('cat.label.font.weight',
2326                 'Cataloging: Spine and pocket label font weight', 'coust', 'label'),
2327             oils_i18n_gettext('cat.label.font.weight',
2328                 'Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".',
2329                 'coust', 'description'),
2330             'string'
2331         )
2332 ;
2333
2334 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2335         'circ.patron_edit.clone.copy_address',
2336         oils_i18n_gettext(
2337             'circ.patron_edit.clone.copy_address',
2338             'Patron Registration: Cloned patrons get address copy',
2339             'coust',
2340             'label'
2341         ),
2342         oils_i18n_gettext(
2343             'circ.patron_edit.clone.copy_address',
2344             'In the Patron editor, copy addresses from the cloned user instead of linking directly to the address',
2345             'coust',
2346             'description'
2347         ),
2348         'bool'
2349 );
2350
2351 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2352         'ui.patron.default_ident_type',
2353         oils_i18n_gettext(
2354             'ui.patron.default_ident_type',
2355             'GUI: Default Ident Type for Patron Registration',
2356             'coust',
2357             'label'),
2358         oils_i18n_gettext(
2359             'ui.patron.default_ident_type',
2360             'This is the default Ident Type for new users in the patron editor.',
2361             'coust',
2362             'description'),
2363         'link',
2364         'cit'
2365 );
2366
2367 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2368         'ui.patron.default_country',
2369         oils_i18n_gettext(
2370             'ui.patron.default_country',
2371             'GUI: Default Country for New Addresses in Patron Editor',
2372             'coust',
2373             'label'),
2374         oils_i18n_gettext(
2375             'ui.patron.default_country',
2376             'This is the default Country for new addresses in the patron editor.',
2377             'coust',
2378             'description'),
2379         'string'
2380 );
2381
2382 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2383         'ui.patron.registration.require_address',
2384         oils_i18n_gettext(
2385             'ui.patron.registration.require_address',
2386             'GUI: Require at least one address for Patron Registration',
2387             'coust',
2388             'label'),
2389         oils_i18n_gettext(
2390             'ui.patron.registration.require_address',
2391             'Enforces a requirement for having at least one address for a patron during registration.',
2392             'coust',
2393             'description'),
2394         'bool'
2395 );
2396
2397 INSERT INTO config.org_unit_setting_type (
2398     name, label, description, datatype
2399 ) VALUES
2400     ('credit.processor.payflowpro.enabled',
2401         'Credit card processing: Enable PayflowPro payments',
2402         'This is NOT the same thing as the settings labeled with just "PayPal."',
2403         'bool'
2404     ),
2405     ('credit.processor.payflowpro.login',
2406         'Credit card processing: PayflowPro login/merchant ID',
2407         'Often the same thing as the PayPal manager login',
2408         'string'
2409     ),
2410     ('credit.processor.payflowpro.password',
2411         'Credit card processing: PayflowPro password',
2412         'PayflowPro password',
2413         'string'
2414     ),
2415     ('credit.processor.payflowpro.testmode',
2416         'Credit card processing: PayflowPro test mode',
2417         'Do not really process transactions, but stay in test mode - uses pilot-payflowpro.paypal.com instead of the usual host',
2418         'bool'
2419     ),
2420     ('credit.processor.payflowpro.vendor',
2421         'Credit card processing: PayflowPro vendor',
2422         'Often the same thing as the login',
2423         'string'
2424     ),
2425     ('credit.processor.payflowpro.partner',
2426         'Credit card processing: PayflowPro partner',
2427         'Often "PayPal" or "VeriSign", sometimes others',
2428         'string'
2429     );
2430
2431 -- Patch the name of an old selfcheck setting
2432 UPDATE actor.org_unit_setting
2433     SET name = 'circ.selfcheck.alert.popup'
2434     WHERE name = 'circ.selfcheck.alert_on_checkout_event';
2435
2436 -- Rename certain existing org_unit settings, if present,
2437 -- and make sure their values are JSON
2438 UPDATE actor.org_unit_setting SET
2439     name = 'circ.holds.default_estimated_wait_interval',
2440     --
2441     -- The value column should be JSON.  The old value should be a number,
2442     -- but it may or may not be quoted.  The following CASE behaves
2443     -- differently depending on whether value is quoted.  It is simplistic,
2444     -- and will be defeated by leading or trailing white space, or various
2445     -- malformations.
2446     --
2447     value = CASE WHEN SUBSTR( value, 1, 1 ) = '"'
2448                 THEN '"' || SUBSTR( value, 2, LENGTH(value) - 2 ) || ' days"'
2449                 ELSE '"' || value || ' days"'
2450             END
2451 WHERE name = 'circ.hold_estimate_wait_interval';
2452
2453 -- Create types for existing org unit settings
2454 -- not otherwise accounted for
2455
2456 INSERT INTO config.org_unit_setting_type(
2457  name,
2458  label,
2459  description
2460 )
2461 SELECT DISTINCT
2462         name,
2463         name,
2464         'FIXME'
2465 FROM
2466         actor.org_unit_setting
2467 WHERE
2468         name NOT IN (
2469                 SELECT name
2470                 FROM config.org_unit_setting_type
2471         );
2472
2473 -- Add foreign key to org_unit_setting
2474
2475 ALTER TABLE actor.org_unit_setting
2476         ADD FOREIGN KEY (name) REFERENCES config.org_unit_setting_type (name)
2477                 DEFERRABLE INITIALLY DEFERRED;
2478
2479 -- Create types for existing user settings
2480 -- not otherwise accounted for
2481
2482 INSERT INTO config.usr_setting_type (
2483         name,
2484         label,
2485         description
2486 )
2487 SELECT DISTINCT
2488         name,
2489         name,
2490         'FIXME'
2491 FROM
2492         actor.usr_setting
2493 WHERE
2494         name NOT IN (
2495                 SELECT name
2496                 FROM config.usr_setting_type
2497         );
2498
2499 -- Add foreign key to user_setting_type
2500
2501 ALTER TABLE actor.usr_setting
2502         ADD FOREIGN KEY (name) REFERENCES config.usr_setting_type (name)
2503                 ON DELETE CASCADE ON UPDATE CASCADE
2504                 DEFERRABLE INITIALLY DEFERRED;
2505
2506 INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES
2507     (1, 'cat.spine.line.margin', 0)
2508     ,(1, 'cat.spine.line.height', 9)
2509     ,(1, 'cat.spine.line.width', 8)
2510     ,(1, 'cat.label.font.family', '"monospace"')
2511     ,(1, 'cat.label.font.size', 10)
2512     ,(1, 'cat.label.font.weight', '"normal"')
2513 ;
2514
2515 ALTER TABLE action_trigger.event_definition ADD COLUMN granularity TEXT;
2516 ALTER TABLE action_trigger.event ADD COLUMN async_output BIGINT REFERENCES action_trigger.event_output (id);
2517 ALTER TABLE action_trigger.event_definition ADD COLUMN usr_field TEXT;
2518 ALTER TABLE action_trigger.event_definition ADD COLUMN opt_in_setting TEXT REFERENCES config.usr_setting_type (name) DEFERRABLE INITIALLY DEFERRED;
2519
2520 CREATE OR REPLACE FUNCTION is_json( TEXT ) RETURNS BOOL AS $f$
2521     use JSON::XS;
2522     my $json = shift();
2523     eval { JSON::XS->new->allow_nonref->decode( $json ) };
2524     return $@ ? 0 : 1;
2525 $f$ LANGUAGE PLPERLU;
2526
2527 ALTER TABLE action_trigger.event ADD COLUMN user_data TEXT CHECK (user_data IS NULL OR is_json( user_data ));
2528
2529 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2530     'hold_request.cancel.expire_no_target',
2531     'ahr',
2532     'A hold is cancelled because no copies were found'
2533 );
2534
2535 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2536     'hold_request.cancel.expire_holds_shelf',
2537     'ahr',
2538     'A hold is cancelled because it was on the holds shelf too long'
2539 );
2540
2541 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2542     'hold_request.cancel.staff',
2543     'ahr',
2544     'A hold is cancelled because it was cancelled by staff'
2545 );
2546
2547 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2548     'hold_request.cancel.patron',
2549     'ahr',
2550     'A hold is cancelled by the patron'
2551 );
2552
2553 -- Fix typos in descriptions
2554 UPDATE action_trigger.hook SET description = 'A hold is successfully placed' WHERE key = 'hold_request.success';
2555 UPDATE action_trigger.hook SET description = 'A hold is attempted but not successfully placed' WHERE key = 'hold_request.failure';
2556
2557 -- Add a hook for renewals
2558 INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('renewal','circ','Item renewed to user');
2559
2560 INSERT INTO action_trigger.validator (module,description) VALUES ('MaxPassiveDelayAge','Check that the event is not too far past the delay_field time -- requires a max_delay_age interval parameter');
2561
2562 -- Sample Pre-due Notice --
2563
2564 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template) 
2565     VALUES (6, 'f', 1, '3 Day Courtesy Notice', 'checkout.due', 'CircIsOpen', 'SendEmail', '-3 days', 'due_date', 'usr', 
2566 $$
2567 [%- USE date -%]
2568 [%- user = target.0.usr -%]
2569 To: [%- params.recipient_email || user.email %]
2570 From: [%- params.sender_email || default_sender %]
2571 Subject: Courtesy Notice
2572
2573 Dear [% user.family_name %], [% user.first_given_name %]
2574 As a reminder, the following items are due in 3 days.
2575
2576 [% FOR circ IN target %]
2577     Title: [% circ.target_copy.call_number.record.simple_record.title %] 
2578     Barcode: [% circ.target_copy.barcode %] 
2579     Due: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2580     Item Cost: [% helpers.get_copy_price(circ.target_copy) %]
2581     Library: [% circ.circ_lib.name %]
2582     Library Phone: [% circ.circ_lib.phone %]
2583 [% END %]
2584
2585 $$);
2586
2587 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2588     (6, 'target_copy.call_number.record.simple_record'),
2589     (6, 'usr'),
2590     (6, 'circ_lib.billing_address');
2591
2592 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2593     (6, 'max_delay_age', '"1 day"');
2594
2595 -- also add the max delay age to the default overdue notice event def
2596 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2597     (1, 'max_delay_age', '"1 day"');
2598   
2599 INSERT INTO action_trigger.validator (module,description) VALUES ('MinPassiveTargetAge','Check that the target is old enough to be used by this event -- requires a min_target_age interval parameter, and accepts an optional target_age_field to specify what time to use for offsetting');
2600
2601 INSERT INTO action_trigger.reactor (module,description) VALUES ('ApplyPatronPenalty','Applies the configured penalty to a patron.  Required named environment variables are "user", which refers to the user object, and "context_org", which refers to the org_unit object that acts as the focus for the penalty.');
2602
2603 INSERT INTO action_trigger.hook (
2604         key,
2605         core_type,
2606         description,
2607         passive
2608     ) VALUES (
2609         'hold_request.shelf_expires_soon',
2610         'ahr',
2611         'A hold on the shelf will expire there soon.',
2612         TRUE
2613     );
2614
2615 INSERT INTO action_trigger.event_definition (
2616         id,
2617         active,
2618         owner,
2619         name,
2620         hook,
2621         validator,
2622         reactor,
2623         delay,
2624         delay_field,
2625         group_field,
2626         template
2627     ) VALUES (
2628         7,
2629         FALSE,
2630         1,
2631         'Hold Expires from Shelf Soon',
2632         'hold_request.shelf_expires_soon',
2633         'HoldIsAvailable',
2634         'SendEmail',
2635         '- 1 DAY',
2636         'shelf_expire_time',
2637         'usr',
2638 $$
2639 [%- USE date -%]
2640 [%- user = target.0.usr -%]
2641 To: [%- params.recipient_email || user.email %]
2642 From: [%- params.sender_email || default_sender %]
2643 Subject: Hold Available Notification
2644
2645 Dear [% user.family_name %], [% user.first_given_name %]
2646 You requested holds on the following item(s), which are available for
2647 pickup, but these holds will soon expire.
2648
2649 [% FOR hold IN target %]
2650     [%- data = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
2651     Title: [% data.title %]
2652     Author: [% data.author %]
2653     Library: [% hold.pickup_lib.name %]
2654 [% END %]
2655 $$
2656     );
2657
2658 INSERT INTO action_trigger.environment (
2659         event_def,
2660         path
2661     ) VALUES
2662     ( 7, 'current_copy'),
2663     ( 7, 'pickup_lib.billing_address'),
2664     ( 7, 'usr');
2665
2666 INSERT INTO action_trigger.hook (
2667         key,
2668         core_type,
2669         description,
2670         passive
2671     ) VALUES (
2672         'hold_request.long_wait',
2673         'ahr',
2674         'A patron has been waiting on a hold to be fulfilled for a long time.',
2675         TRUE
2676     );
2677
2678 INSERT INTO action_trigger.event_definition (
2679         id,
2680         active,
2681         owner,
2682         name,
2683         hook,
2684         validator,
2685         reactor,
2686         delay,
2687         delay_field,
2688         group_field,
2689         template
2690     ) VALUES (
2691         9,
2692         FALSE,
2693         1,
2694         'Hold waiting for pickup for long time',
2695         'hold_request.long_wait',
2696         'NOOP_True',
2697         'SendEmail',
2698         '6 MONTHS',
2699         'request_time',
2700         'usr',
2701 $$
2702 [%- USE date -%]
2703 [%- user = target.0.usr -%]
2704 To: [%- params.recipient_email || user.email %]
2705 From: [%- params.sender_email || default_sender %]
2706 Subject: Long Wait Hold Notification
2707
2708 Dear [% user.family_name %], [% user.first_given_name %]
2709
2710 You requested hold(s) on the following item(s), but unfortunately
2711 we have not been able to fulfill your request after a considerable
2712 length of time.  If you would still like to receive these items,
2713 no action is required.
2714
2715 [% FOR hold IN target %]
2716     Title: [% hold.bib_rec.bib_record.simple_record.title %]
2717     Author: [% hold.bib_rec.bib_record.simple_record.author %]
2718 [% END %]
2719 $$
2720 );
2721
2722 INSERT INTO action_trigger.environment (
2723         event_def,
2724         path
2725     ) VALUES
2726     (9, 'pickup_lib'),
2727     (9, 'usr'),
2728     (9, 'bib_rec.bib_record.simple_record');
2729
2730 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2731     VALUES (
2732         'format.selfcheck.checkout',
2733         'circ',
2734         'Formats circ objects for self-checkout receipt',
2735         TRUE
2736     );
2737
2738 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2739     VALUES (
2740         10,
2741         TRUE,
2742         1,
2743         'Self-Checkout Receipt',
2744         'format.selfcheck.checkout',
2745         'NOOP_True',
2746         'ProcessTemplate',
2747         'usr',
2748         'print-on-demand',
2749 $$
2750 [%- USE date -%]
2751 [%- SET user = target.0.usr -%]
2752 [%- SET lib = target.0.circ_lib -%]
2753 [%- SET lib_addr = target.0.circ_lib.billing_address -%]
2754 [%- SET hours = lib.hours_of_operation -%]
2755 <div>
2756     <style> li { padding: 8px; margin 5px; }</style>
2757     <div>[% date.format %]</div>
2758     <div>[% lib.name %]</div>
2759     <div>[% lib_addr.street1 %] [% lib_addr.street2 %]</div>
2760     <div>[% lib_addr.city %], [% lib_addr.state %] [% lb_addr.post_code %]</div>
2761     <div>[% lib.phone %]</div>
2762     <br/>
2763
2764     [% user.family_name %], [% user.first_given_name %]
2765     <ol>
2766     [% FOR circ IN target %]
2767         [%-
2768             SET idx = loop.count - 1;
2769             SET udata =  user_data.$idx
2770         -%]
2771         <li>
2772             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2773             <div>Barcode: [% circ.target_copy.barcode %]</div>
2774             [% IF udata.renewal_failure %]
2775                 <div style='color:red;'>Renewal Failed</div>
2776             [% ELSE %]
2777                 <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2778             [% END %]
2779         </li>
2780     [% END %]
2781     </ol>
2782     
2783     <div>
2784         Library Hours
2785         [%- BLOCK format_time; date.format(time _ ' 1/1/1000', format='%I:%M %p'); END -%]
2786         <div>
2787             Monday 
2788             [% PROCESS format_time time = hours.dow_0_open %] 
2789             [% PROCESS format_time time = hours.dow_0_close %] 
2790         </div>
2791         <div>
2792             Tuesday 
2793             [% PROCESS format_time time = hours.dow_1_open %] 
2794             [% PROCESS format_time time = hours.dow_1_close %] 
2795         </div>
2796         <div>
2797             Wednesday 
2798             [% PROCESS format_time time = hours.dow_2_open %] 
2799             [% PROCESS format_time time = hours.dow_2_close %] 
2800         </div>
2801         <div>
2802             Thursday
2803             [% PROCESS format_time time = hours.dow_3_open %] 
2804             [% PROCESS format_time time = hours.dow_3_close %] 
2805         </div>
2806         <div>
2807             Friday
2808             [% PROCESS format_time time = hours.dow_4_open %] 
2809             [% PROCESS format_time time = hours.dow_4_close %] 
2810         </div>
2811         <div>
2812             Saturday
2813             [% PROCESS format_time time = hours.dow_5_open %] 
2814             [% PROCESS format_time time = hours.dow_5_close %] 
2815         </div>
2816         <div>
2817             Sunday 
2818             [% PROCESS format_time time = hours.dow_6_open %] 
2819             [% PROCESS format_time time = hours.dow_6_close %] 
2820         </div>
2821     </div>
2822 </div>
2823 $$
2824 );
2825
2826 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2827     ( 10, 'target_copy'),
2828     ( 10, 'circ_lib.billing_address'),
2829     ( 10, 'circ_lib.hours_of_operation'),
2830     ( 10, 'usr');
2831
2832 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2833     VALUES (
2834         'format.selfcheck.items_out',
2835         'circ',
2836         'Formats items out for self-checkout receipt',
2837         TRUE
2838     );
2839
2840 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2841     VALUES (
2842         11,
2843         TRUE,
2844         1,
2845         'Self-Checkout Items Out Receipt',
2846         'format.selfcheck.items_out',
2847         'NOOP_True',
2848         'ProcessTemplate',
2849         'usr',
2850         'print-on-demand',
2851 $$
2852 [%- USE date -%]
2853 [%- SET user = target.0.usr -%]
2854 <div>
2855     <style> li { padding: 8px; margin 5px; }</style>
2856     <div>[% date.format %]</div>
2857     <br/>
2858
2859     [% user.family_name %], [% user.first_given_name %]
2860     <ol>
2861     [% FOR circ IN target %]
2862         <li>
2863             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2864             <div>Barcode: [% circ.target_copy.barcode %]</div>
2865             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2866         </li>
2867     [% END %]
2868     </ol>
2869 </div>
2870 $$
2871 );
2872
2873
2874 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2875     ( 11, 'target_copy'),
2876     ( 11, 'circ_lib.billing_address'),
2877     ( 11, 'circ_lib.hours_of_operation'),
2878     ( 11, 'usr');
2879
2880 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2881     VALUES (
2882         'format.selfcheck.holds',
2883         'ahr',
2884         'Formats holds for self-checkout receipt',
2885         TRUE
2886     );
2887
2888 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2889     VALUES (
2890         12,
2891         TRUE,
2892         1,
2893         'Self-Checkout Holds Receipt',
2894         'format.selfcheck.holds',
2895         'NOOP_True',
2896         'ProcessTemplate',
2897         'usr',
2898         'print-on-demand',
2899 $$
2900 [%- USE date -%]
2901 [%- SET user = target.0.usr -%]
2902 <div>
2903     <style> li { padding: 8px; margin 5px; }</style>
2904     <div>[% date.format %]</div>
2905     <br/>
2906
2907     [% user.family_name %], [% user.first_given_name %]
2908     <ol>
2909     [% FOR hold IN target %]
2910         [%-
2911             SET idx = loop.count - 1;
2912             SET udata =  user_data.$idx
2913         -%]
2914         <li>
2915             <div>Title: [% hold.bib_rec.bib_record.simple_record.title %]</div>
2916             <div>Author: [% hold.bib_rec.bib_record.simple_record.author %]</div>
2917             <div>Pickup Location: [% hold.pickup_lib.name %]</div>
2918             <div>Status: 
2919                 [%- IF udata.ready -%]
2920                     Ready for pickup
2921                 [% ELSE %]
2922                     #[% udata.queue_position %] of [% udata.potential_copies %] copies.
2923                 [% END %]
2924             </div>
2925         </li>
2926     [% END %]
2927     </ol>
2928 </div>
2929 $$
2930 );
2931
2932
2933 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2934     ( 12, 'bib_rec.bib_record.simple_record'),
2935     ( 12, 'pickup_lib'),
2936     ( 12, 'usr');
2937
2938 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2939     VALUES (
2940         'format.selfcheck.fines',
2941         'au',
2942         'Formats fines for self-checkout receipt',
2943         TRUE
2944     );
2945
2946 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template )
2947     VALUES (
2948         13,
2949         TRUE,
2950         1,
2951         'Self-Checkout Fines Receipt',
2952         'format.selfcheck.fines',
2953         'NOOP_True',
2954         'ProcessTemplate',
2955         'print-on-demand',
2956 $$
2957 [%- USE date -%]
2958 [%- SET user = target -%]
2959 <div>
2960     <style> li { padding: 8px; margin 5px; }</style>
2961     <div>[% date.format %]</div>
2962     <br/>
2963
2964     [% user.family_name %], [% user.first_given_name %]
2965     <ol>
2966     [% FOR xact IN user.open_billable_transactions_summary %]
2967         <li>
2968             <div>Details: 
2969                 [% IF xact.xact_type == 'circulation' %]
2970                     [%- helpers.get_copy_bib_basics(xact.circulation.target_copy).title -%]
2971                 [% ELSE %]
2972                     [%- xact.last_billing_type -%]
2973                 [% END %]
2974             </div>
2975             <div>Total Billed: [% xact.total_owed %]</div>
2976             <div>Total Paid: [% xact.total_paid %]</div>
2977             <div>Balance Owed : [% xact.balance_owed %]</div>
2978         </li>
2979     [% END %]
2980     </ol>
2981 </div>
2982 $$
2983 );
2984
2985 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2986     ( 13, 'open_billable_transactions_summary.circulation' );
2987
2988 INSERT INTO action_trigger.reactor (module,description) VALUES
2989 (   'SendFile',
2990     oils_i18n_gettext(
2991         'SendFile',
2992         'Build and transfer a file to a remote server.  Required parameter "remote_host" specifying target server.  Optional parameters: remote_user, remote_password, remote_account, port, type (FTP, SFTP or SCP), and debug.',
2993         'atreact',
2994         'description'
2995     )
2996 );
2997
2998 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2999     VALUES (
3000         'format.acqli.html',
3001         'jub',
3002         'Formats lineitem worksheet for titles received',
3003         TRUE
3004     );
3005
3006 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template)
3007     VALUES (
3008         14,
3009         TRUE,
3010         1,
3011         'Lineitem Worksheet',
3012         'format.acqli.html',
3013         'NOOP_True',
3014         'ProcessTemplate',
3015         'print-on-demand',
3016 $$
3017 [%- USE date -%]
3018 [%- SET li = target; -%]
3019 <div class="wrapper">
3020     <div class="summary" style='font-size:110%; font-weight:bold;'>
3021
3022         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
3023         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
3024         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
3025         <div class="lineid">Lineitem ID: [% li.id %]</div>
3026
3027         [% IF li.distribution_formulas.size > 0 %]
3028             [% SET forms = [] %]
3029             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
3030             <div>Distribution Formulas: [% forms.join(',') %]</div>
3031         [% END %]
3032
3033         [% IF li.lineitem_notes.size > 0 %]
3034             Lineitem Notes:
3035             <ul>
3036                 [%- FOR note IN li.lineitem_notes -%]
3037                     <li>
3038                     [% IF note.alert_text %]
3039                         [% note.alert_text.code -%] 
3040                         [% IF note.value -%]
3041                             : [% note.value %]
3042                         [% END %]
3043                     [% ELSE %]
3044                         [% note.value -%] 
3045                     [% END %]
3046                     </li>
3047                 [% END %]
3048             </ul>
3049         [% END %]
3050     </div>
3051     <br/>
3052     <table>
3053         <thead>
3054             <tr>
3055                 <th>Branch</th>
3056                 <th>Barcode</th>
3057                 <th>Call Number</th>
3058                 <th>Fund</th>
3059                 <th>Recd.</th>
3060                 <th>Notes</th>
3061             </tr>
3062         </thead>
3063         <tbody>
3064         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
3065             [% 
3066                 IF copy.eg_copy_id;
3067                     SET copy = copy.eg_copy_id;
3068                     SET cn_label = copy.call_number.label;
3069                 ELSE; 
3070                     SET copy = detail; 
3071                     SET cn_label = detail.cn_label;
3072                 END 
3073             %]
3074             <tr>
3075                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
3076                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
3077                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
3078                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
3079                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
3080                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
3081                 <td style='padding:5px;'>[% detail.note %]</td>
3082             </tr>
3083         [% END %]
3084         </tbody>
3085     </table>
3086 </div>
3087 $$
3088 );
3089
3090
3091 INSERT INTO action_trigger.environment (event_def, path) VALUES
3092     ( 14, 'attributes' ),
3093     ( 14, 'lineitem_details' ),
3094     ( 14, 'lineitem_details.owning_lib' ),
3095     ( 14, 'lineitem_notes' )
3096 ;
3097
3098 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3099         'aur.ordered',
3100         'aur', 
3101         oils_i18n_gettext(
3102             'aur.ordered',
3103             'A patron acquisition request has been marked On-Order.',
3104             'ath',
3105             'description'
3106         ), 
3107         TRUE
3108     ), (
3109         'aur.received', 
3110         'aur', 
3111         oils_i18n_gettext(
3112             'aur.received', 
3113             'A patron acquisition request has been marked Received.',
3114             'ath',
3115             'description'
3116         ),
3117         TRUE
3118     ), (
3119         'aur.cancelled',
3120         'aur',
3121         oils_i18n_gettext(
3122             'aur.cancelled',
3123             'A patron acquisition request has been marked Cancelled.',
3124             'ath',
3125             'description'
3126         ),
3127         TRUE
3128     )
3129 ;
3130
3131 INSERT INTO action_trigger.validator (module,description) VALUES (
3132         'Acq::UserRequestOrdered',
3133         oils_i18n_gettext(
3134             'Acq::UserRequestOrdered',
3135             'Tests to see if the corresponding Line Item has a state of "on-order".',
3136             'atval',
3137             'description'
3138         )
3139     ), (
3140         'Acq::UserRequestReceived',
3141         oils_i18n_gettext(
3142             'Acq::UserRequestReceived',
3143             'Tests to see if the corresponding Line Item has a state of "received".',
3144             'atval',
3145             'description'
3146         )
3147     ), (
3148         'Acq::UserRequestCancelled',
3149         oils_i18n_gettext(
3150             'Acq::UserRequestCancelled',
3151             'Tests to see if the corresponding Line Item has a state of "cancelled".',
3152             'atval',
3153             'description'
3154         )
3155     )
3156 ;
3157
3158 -- What was event_definition #15 in v1.6.1 will be recreated as #20.  This
3159 -- renumbering requires some juggling:
3160 --
3161 -- 1. Update any child rows to point to #20.  These updates will temporarily
3162 -- violate foreign key constraints, but that's okay as long as we create
3163 -- #20 before committing.
3164 --
3165 -- 2. Delete the old #15.
3166 --
3167 -- 3. Insert the new #15.
3168 --
3169 -- 4. Insert #20.
3170 --
3171 -- We could combine steps 2 and 3 into a single update, but that would create
3172 -- additional opportunities for typos, since we already have the insert from
3173 -- an upgrade script.
3174
3175 UPDATE action_trigger.environment
3176 SET event_def = 20
3177 WHERE event_def = 15;
3178
3179 UPDATE action_trigger.event
3180 SET event_def = 20
3181 WHERE event_def = 15;
3182
3183 UPDATE action_trigger.event_params
3184 SET event_def = 20
3185 WHERE event_def = 15;
3186
3187 DELETE FROM action_trigger.event_definition
3188 WHERE id = 15;
3189
3190 INSERT INTO action_trigger.event_definition (
3191         id,
3192         active,
3193         owner,
3194         name,
3195         hook,
3196         validator,
3197         reactor,
3198         template
3199     ) VALUES (
3200         15,
3201         FALSE,
3202         1,
3203         'Email Notice: Patron Acquisition Request marked On-Order.',
3204         'aur.ordered',
3205         'Acq::UserRequestOrdered',
3206         'SendEmail',
3207 $$
3208 [%- USE date -%]
3209 [%- SET li = target.lineitem; -%]
3210 [%- SET user = target.usr -%]
3211 [%- SET title = helpers.get_li_attr("title", "", li.attributes) -%]
3212 [%- SET author = helpers.get_li_attr("author", "", li.attributes) -%]
3213 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) -%]
3214 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) -%]
3215 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3216 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3217
3218 To: [%- params.recipient_email || user.email %]
3219 From: [%- params.sender_email || default_sender %]
3220 Subject: Acquisition Request Notification
3221
3222 Dear [% user.family_name %], [% user.first_given_name %]
3223 Our records indicate the following acquisition request has been placed on order.
3224
3225 Title: [% title %]
3226 [% IF author %]Author: [% author %][% END %]
3227 [% IF edition %]Edition: [% edition %][% END %]
3228 [% IF isbn %]ISBN: [% isbn %][% END %]
3229 [% IF publisher %]Publisher: [% publisher %][% END %]
3230 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3231 Lineitem ID: [% li.id %]
3232 $$
3233     ), (
3234         16,
3235         FALSE,
3236         1,
3237         'Email Notice: Patron Acquisition Request marked Received.',
3238         'aur.received',
3239         'Acq::UserRequestReceived',
3240         'SendEmail',
3241 $$
3242 [%- USE date -%]
3243 [%- SET li = target.lineitem; -%]
3244 [%- SET user = target.usr -%]
3245 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3246 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3247 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3248 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3249 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3250 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3251
3252 To: [%- params.recipient_email || user.email %]
3253 From: [%- params.sender_email || default_sender %]
3254 Subject: Acquisition Request Notification
3255
3256 Dear [% user.family_name %], [% user.first_given_name %]
3257 Our records indicate the materials for the following acquisition request have been received.
3258
3259 Title: [% title %]
3260 [% IF author %]Author: [% author %][% END %]
3261 [% IF edition %]Edition: [% edition %][% END %]
3262 [% IF isbn %]ISBN: [% isbn %][% END %]
3263 [% IF publisher %]Publisher: [% publisher %][% END %]
3264 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3265 Lineitem ID: [% li.id %]
3266 $$
3267     ), (
3268         17,
3269         FALSE,
3270         1,
3271         'Email Notice: Patron Acquisition Request marked Cancelled.',
3272         'aur.cancelled',
3273         'Acq::UserRequestCancelled',
3274         'SendEmail',
3275 $$
3276 [%- USE date -%]
3277 [%- SET li = target.lineitem; -%]
3278 [%- SET user = target.usr -%]
3279 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3280 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3281 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3282 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3283 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3284 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3285
3286 To: [%- params.recipient_email || user.email %]
3287 From: [%- params.sender_email || default_sender %]
3288 Subject: Acquisition Request Notification
3289
3290 Dear [% user.family_name %], [% user.first_given_name %]
3291 Our records indicate the following acquisition request has been cancelled.
3292
3293 Title: [% title %]
3294 [% IF author %]Author: [% author %][% END %]
3295 [% IF edition %]Edition: [% edition %][% END %]
3296 [% IF isbn %]ISBN: [% isbn %][% END %]
3297 [% IF publisher %]Publisher: [% publisher %][% END %]
3298 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3299 Lineitem ID: [% li.id %]
3300 $$
3301     );
3302
3303 INSERT INTO action_trigger.environment (
3304         event_def,
3305         path
3306     ) VALUES 
3307         ( 15, 'lineitem' ),
3308         ( 15, 'lineitem.attributes' ),
3309         ( 15, 'usr' ),
3310
3311         ( 16, 'lineitem' ),
3312         ( 16, 'lineitem.attributes' ),
3313         ( 16, 'usr' ),
3314
3315         ( 17, 'lineitem' ),
3316         ( 17, 'lineitem.attributes' ),
3317         ( 17, 'usr' )
3318     ;
3319
3320 INSERT INTO action_trigger.event_definition
3321 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
3322 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
3323 $$
3324 [%- USE date -%]
3325 [%# start JEDI document 
3326   # Vendor specific kludges:
3327   # BT      - vendcode goes to NAD/BY *suffix*  w/ 91 qualifier
3328   # INGRAM  - vendcode goes to NAD/BY *segment* w/ 91 qualifier (separately)
3329   # BRODART - vendcode goes to FTX segment (lineitem level)
3330 -%]
3331 [%- 
3332 IF target.provider.edi_default.vendcode && target.provider.code == 'BRODART';
3333     xtra_ftx = target.provider.edi_default.vendcode;
3334 END;
3335 -%]
3336 [%- BLOCK big_block -%]
3337 {
3338    "recipient":"[% target.provider.san %]",
3339    "sender":"[% target.ordering_agency.mailing_address.san %]",
3340    "body": [{
3341      "ORDERS":[ "order", {
3342         "po_number":[% target.id %],
3343         "date":"[% date.format(date.now, '%Y%m%d') %]",
3344         "buyer":[
3345             [%   IF   target.provider.edi_default.vendcode && (target.provider.code == 'BT' || target.provider.name.match('(?i)^BAKER & TAYLOR'))  -%]
3346                 {"id-qualifier": 91, "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]"}
3347             [%- ELSIF target.provider.edi_default.vendcode && target.provider.code == 'INGRAM' -%]
3348                 {"id":"[% target.ordering_agency.mailing_address.san %]"},
3349                 {"id-qualifier": 91, "id":"[% target.provider.edi_default.vendcode %]"}
3350             [%- ELSE -%]
3351                 {"id":"[% target.ordering_agency.mailing_address.san %]"}
3352             [%- END -%]
3353         ],
3354         "vendor":[
3355             [%- # target.provider.name (target.provider.id) -%]
3356             "[% target.provider.san %]",
3357             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
3358         ],
3359         "currency":"[% target.provider.currency_type %]",
3360                 
3361         "items":[
3362         [%- FOR li IN target.lineitems %]
3363         {
3364             "line_index":"[% li.id %]",
3365             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
3366             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
3367                 [% IF isbn.length == 13 -%]
3368                 {"id-qualifier":"EN","id":"[% isbn %]"},
3369                 [% ELSE -%]
3370                 {"id-qualifier":"IB","id":"[% isbn %]"},
3371                 [%- END %]
3372             [% END %]
3373                 {"id-qualifier":"IN","id":"[% li.id %]"}
3374             ],
3375             "price":[% li.estimated_unit_price || '0.00' %],
3376             "desc":[
3377                 {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"},
3378                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
3379                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
3380                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
3381             ],
3382             [%- ftx_vals = []; 
3383                 FOR note IN li.lineitem_notes; 
3384                     NEXT UNLESS note.vendor_public == 't'; 
3385                     ftx_vals.push(note.value); 
3386                 END; 
3387                 IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
3388                 IF ftx_vals.size == 0; ftx_vals.unshift('');       END;  # BT needs FTX+LIN for every LI, even if it is an empty one
3389             -%]
3390
3391             "free-text":[ 
3392                 [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
3393             ],            
3394             "quantity":[% li.lineitem_details.size %]
3395         }[% UNLESS loop.last %],[% END %]
3396         [%-# TODO: lineitem details (later) -%]
3397         [% END %]
3398         ],
3399         "line_items":[% target.lineitems.size %]
3400      }]  [%# close ORDERS array %]
3401    }]    [%# close  body  array %]
3402 }
3403 [% END %]
3404 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
3405
3406 $$);
3407
3408 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3409   (23, 'lineitems.attributes'), 
3410   (23, 'lineitems.lineitem_details'), 
3411   (23, 'lineitems.lineitem_notes'), 
3412   (23, 'ordering_agency.mailing_address'), 
3413   (23, 'provider');
3414
3415 UPDATE action_trigger.event_definition SET template = 
3416 $$
3417 [%- USE date -%]
3418 [%-
3419     # find a lineitem attribute by name and optional type
3420     BLOCK get_li_attr;
3421         FOR attr IN li.attributes;
3422             IF attr.attr_name == attr_name;
3423                 IF !attr_type OR attr_type == attr.attr_type;
3424                     attr.attr_value;
3425                     LAST;
3426                 END;
3427             END;
3428         END;
3429     END
3430 -%]
3431
3432 <h2>Purchase Order [% target.id %]</h2>
3433 <br/>
3434 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
3435 <br/>
3436
3437 <style>
3438     table td { padding:5px; border:1px solid #aaa;}
3439     table { width:95%; border-collapse:collapse; }
3440     #vendor-notes { padding:5px; border:1px solid #aaa; }
3441 </style>
3442 <table id='vendor-table'>
3443   <tr>
3444     <td valign='top'>Vendor</td>
3445     <td>
3446       <div>[% target.provider.name %]</div>
3447       <div>[% target.provider.addresses.0.street1 %]</div>
3448       <div>[% target.provider.addresses.0.street2 %]</div>
3449       <div>[% target.provider.addresses.0.city %]</div>
3450       <div>[% target.provider.addresses.0.state %]</div>
3451       <div>[% target.provider.addresses.0.country %]</div>
3452       <div>[% target.provider.addresses.0.post_code %]</div>
3453     </td>
3454     <td valign='top'>Ship to / Bill to</td>
3455     <td>
3456       <div>[% target.ordering_agency.name %]</div>
3457       <div>[% target.ordering_agency.billing_address.street1 %]</div>
3458       <div>[% target.ordering_agency.billing_address.street2 %]</div>
3459       <div>[% target.ordering_agency.billing_address.city %]</div>
3460       <div>[% target.ordering_agency.billing_address.state %]</div>
3461       <div>[% target.ordering_agency.billing_address.country %]</div>
3462       <div>[% target.ordering_agency.billing_address.post_code %]</div>
3463     </td>
3464   </tr>
3465 </table>
3466
3467 <br/><br/>
3468 <fieldset id='vendor-notes'>
3469     <legend>Notes to the Vendor</legend>
3470     <ul>
3471     [% FOR note IN target.notes %]
3472         [% IF note.vendor_public == 't' %]
3473             <li>[% note.value %]</li>
3474         [% END %]
3475     [% END %]
3476     </ul>
3477 </fieldset>
3478 <br/><br/>
3479
3480 <table>
3481   <thead>
3482     <tr>
3483       <th>PO#</th>
3484       <th>ISBN or Item #</th>
3485       <th>Title</th>
3486       <th>Quantity</th>
3487       <th>Unit Price</th>
3488       <th>Line Total</th>
3489       <th>Notes</th>
3490     </tr>
3491   </thead>
3492   <tbody>
3493
3494   [% subtotal = 0 %]
3495   [% FOR li IN target.lineitems %]
3496
3497   <tr>
3498     [% count = li.lineitem_details.size %]
3499     [% price = li.estimated_unit_price %]
3500     [% litotal = (price * count) %]
3501     [% subtotal = subtotal + litotal %]
3502     [% isbn = PROCESS get_li_attr attr_name = 'isbn' %]
3503     [% ident = PROCESS get_li_attr attr_name = 'identifier' %]
3504
3505     <td>[% target.id %]</td>
3506     <td>[% isbn || ident %]</td>
3507     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
3508     <td>[% count %]</td>
3509     <td>[% price %]</td>
3510     <td>[% litotal %]</td>
3511     <td>
3512         <ul>
3513         [% FOR note IN li.lineitem_notes %]
3514             [% IF note.vendor_public == 't' %]
3515                 <li>[% note.value %]</li>
3516             [% END %]
3517         [% END %]
3518         </ul>
3519     </td>
3520   </tr>
3521   [% END %]
3522   <tr>
3523     <td/><td/><td/><td/>
3524     <td>Subtotal</td>
3525     <td>[% subtotal %]</td>
3526   </tr>
3527   </tbody>
3528 </table>
3529
3530 <br/>
3531
3532 Total Line Item Count: [% target.lineitems.size %]
3533 $$
3534 WHERE id = 4;
3535
3536 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3537     (4, 'lineitems.lineitem_notes'),
3538     (4, 'notes');
3539
3540 INSERT INTO action_trigger.environment (event_def, path) VALUES
3541     ( 14, 'lineitem_notes.alert_text' ),
3542     ( 14, 'distribution_formulas.formula' ),
3543     ( 14, 'lineitem_details.fund' ),
3544     ( 14, 'lineitem_details.eg_copy_id' ),
3545     ( 14, 'lineitem_details.eg_copy_id.call_number' )
3546 ;
3547
3548 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3549         'aur.created',
3550         'aur',
3551         oils_i18n_gettext(
3552             'aur.created',
3553             'A patron has made an acquisitions request.',
3554             'ath',
3555             'description'
3556         ),
3557         TRUE
3558     ), (
3559         'aur.rejected',
3560         'aur',
3561         oils_i18n_gettext(
3562             'aur.rejected',
3563             'A patron acquisition request has been rejected.',
3564             'ath',
3565             'description'
3566         ),
3567         TRUE
3568     )
3569 ;
3570
3571 INSERT INTO action_trigger.event_definition (
3572         id,
3573         active,
3574         owner,
3575         name,
3576         hook,
3577         validator,
3578         reactor,
3579         template
3580     ) VALUES (
3581         18,
3582         FALSE,
3583         1,
3584         'Email Notice: Acquisition Request created.',
3585         'aur.created',
3586         'NOOP_True',
3587         'SendEmail',
3588 $$
3589 [%- USE date -%]
3590 [%- SET user = target.usr -%]
3591 [%- SET title = target.title -%]
3592 [%- SET author = target.author -%]
3593 [%- SET isxn = target.isxn -%]
3594 [%- SET publisher = target.publisher -%]
3595 [%- SET pubdate = target.pubdate -%]
3596
3597 To: [%- params.recipient_email || user.email %]
3598 From: [%- params.sender_email || default_sender %]
3599 Subject: Acquisition Request Notification
3600
3601 Dear [% user.family_name %], [% user.first_given_name %]
3602 Our records indicate that you have made the following acquisition request:
3603
3604 Title: [% title %]
3605 [% IF author %]Author: [% author %][% END %]
3606 [% IF edition %]Edition: [% edition %][% END %]
3607 [% IF isbn %]ISXN: [% isxn %][% END %]
3608 [% IF publisher %]Publisher: [% publisher %][% END %]
3609 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3610 $$
3611     ), (
3612         19,
3613         FALSE,
3614         1,
3615         'Email Notice: Acquisition Request Rejected.',
3616         'aur.rejected',
3617         'NOOP_True',
3618         'SendEmail',
3619 $$
3620 [%- USE date -%]
3621 [%- SET user = target.usr -%]
3622 [%- SET title = target.title -%]
3623 [%- SET author = target.author -%]
3624 [%- SET isxn = target.isxn -%]
3625 [%- SET publisher = target.publisher -%]
3626 [%- SET pubdate = target.pubdate -%]
3627 [%- SET cancel_reason = target.cancel_reason.description -%]
3628
3629 To: [%- params.recipient_email || user.email %]
3630 From: [%- params.sender_email || default_sender %]
3631 Subject: Acquisition Request Notification
3632
3633 Dear [% user.family_name %], [% user.first_given_name %]
3634 Our records indicate the following acquisition request has been rejected for this reason: [% cancel_reason %]
3635
3636 Title: [% title %]
3637 [% IF author %]Author: [% author %][% END %]
3638 [% IF edition %]Edition: [% edition %][% END %]
3639 [% IF isbn %]ISBN: [% isbn %][% END %]
3640 [% IF publisher %]Publisher: [% publisher %][% END %]
3641 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3642 $$
3643     );
3644
3645 INSERT INTO action_trigger.environment (
3646         event_def,
3647         path
3648     ) VALUES 
3649         ( 18, 'usr' ),
3650         ( 19, 'usr' ),
3651         ( 19, 'cancel_reason' )
3652     ;
3653
3654 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, template) 
3655     VALUES (20, 'f', 1, 'Password reset request notification', 'password.reset_request', 'NOOP_True', 'SendEmail', '00:00:01',
3656 $$
3657 [%- USE date -%]
3658 [%- user = target.usr -%]
3659 To: [%- params.recipient_email || user.email %]
3660 From: [%- params.sender_email || user.home_ou.email || default_sender %]
3661 Subject: [% user.home_ou.name %]: library account password reset request
3662   
3663 You have received this message because you, or somebody else, requested a reset
3664 of your library system password. If you did not request a reset of your library
3665 system password, just ignore this message and your current password will
3666 continue to work.
3667
3668 If you did request a reset of your library system password, please perform
3669 the following steps to continue the process of resetting your password:
3670
3671 1. Open the following link in a web browser: https://[% params.hostname %]/opac/password/[% params.locale || 'en-US' %]/[% target.uuid %]
3672 The browser displays a password reset form.
3673
3674 2. Enter your new password in the password reset form in the browser. You must
3675 enter the password twice to ensure that you do not make a mistake. If the
3676 passwords match, you will then be able to log in to your library system account
3677 with the new password.
3678
3679 $$);
3680
3681 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3682     VALUES (
3683         'format.acqcle.html',
3684         'acqcle',
3685         'Formats claim events into a voucher',
3686         TRUE
3687     );
3688
3689 INSERT INTO action_trigger.event_definition (
3690         id, active, owner, name, hook, group_field,
3691         validator, reactor, granularity, template
3692     ) VALUES (
3693         21,
3694         TRUE,
3695         1,
3696         'Claim Voucher',
3697         'format.acqcle.html',
3698         'claim',
3699         'NOOP_True',
3700         'ProcessTemplate',
3701         'print-on-demand',
3702 $$
3703 [%- USE date -%]
3704 [%- SET claim = target.0.claim -%]
3705 <!-- This will need refined/prettified. -->
3706 <div class="acq-claim-voucher">
3707     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
3708     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
3709     <ul>
3710         [% FOR event IN target %]
3711         <li>
3712             Event type: [% event.type.code %]
3713             [% IF event.type.library_initiated %](Library initiated)[% END %]
3714             <br />
3715             Event date: [% event.event_date %]<br />
3716             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
3717             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
3718             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
3719             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
3720             [% event.claim.lineitem_detail.fund.code %]
3721             ([% event.claim.lineitem_detail.fund.year %])
3722         </li>
3723         [% END %]
3724     </ul>
3725 </div>
3726 $$
3727 );
3728
3729 INSERT INTO action_trigger.environment (event_def, path) VALUES
3730     (21, 'claim'),
3731     (21, 'claim.type'),
3732     (21, 'claim.lineitem_detail'),
3733     (21, 'claim.lineitem_detail.fund'),
3734     (21, 'claim.lineitem_detail.lineitem.attributes'),
3735     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
3736     (21, 'creator'),
3737     (21, 'type')
3738 ;
3739
3740 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3741     VALUES (
3742         'format.acqinv.html',
3743         'acqinv',
3744         'Formats invoices into a voucher',
3745         TRUE
3746     );
3747
3748 INSERT INTO action_trigger.event_definition (
3749         id, active, owner, name, hook,
3750         validator, reactor, granularity, template
3751     ) VALUES (
3752         22,
3753         TRUE,
3754         1,
3755         'Invoice',
3756         'format.acqinv.html',
3757         'NOOP_True',
3758         'ProcessTemplate',
3759         'print-on-demand',
3760 $$
3761 [% FILTER collapse %]
3762 [%- SET invoice = target -%]
3763 <!-- This lacks totals, info about funds (for invoice entries,
3764     funds are per-LID!), and general refinement -->
3765 <div class="acq-invoice-voucher">
3766     <h1>Invoice</h1>
3767     <div>
3768         <strong>No.</strong> [% invoice.inv_ident %]
3769         [% IF invoice.inv_type %]
3770             / <strong>Type:</strong>[% invoice.inv_type %]
3771         [% END %]
3772     </div>
3773     <div>
3774         <dl>
3775             [% BLOCK ent_with_address %]
3776             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3777             <dd>
3778                 [% IF ent.addresses.0 %]
3779                     [% SET addr = ent.addresses.0 %]
3780                     [% addr.street1 %]<br />
3781                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3782                     [% addr.city %],
3783                     [% IF addr.county %] [% addr.county %], [% END %]
3784                     [% IF addr.state %] [% addr.state %] [% END %]
3785                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3786                     [% IF addr.country %] [% addr.country %] [% END %]
3787                 [% END %]
3788                 <p>
3789                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3790                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3791                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3792                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3793                 </p>
3794             </dd>
3795             [% END %]
3796             [% INCLUDE ent_with_address
3797                 ent = invoice.provider
3798                 ent_label = "Provider" %]
3799             [% INCLUDE ent_with_address
3800                 ent = invoice.shipper
3801                 ent_label = "Shipper" %]
3802             <dt>Receiver</dt>
3803             <dd>
3804                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3805             </dd>
3806             <dt>Received</dt>
3807             <dd>
3808                 [% helpers.format_date(invoice.recv_date) %] by
3809                 [% invoice.recv_method %]
3810             </dd>
3811             [% IF invoice.note %]
3812                 <dt>Note</dt>
3813                 <dd>
3814                     [% invoice.note %]
3815                 </dd>
3816             [% END %]
3817         </dl>
3818     </div>
3819     <ul>
3820         [% FOR entry IN invoice.entries %]
3821             <li>
3822                 [% IF entry.lineitem %]
3823                     Title: [% helpers.get_li_attr(
3824                         "title", "", entry.lineitem.attributes
3825                     ) %]<br />
3826                     Author: [% helpers.get_li_attr(
3827                         "author", "", entry.lineitem.attributes
3828                     ) %]
3829                 [% END %]
3830                 [% IF entry.purchase_order %]
3831                     (PO: [% entry.purchase_order.name %])
3832                 [% END %]<br />
3833                 Invoice item count: [% entry.inv_item_count %]
3834                 [% IF entry.phys_item_count %]
3835                     / Physical item count: [% entry.phys_item_count %]
3836                 [% END %]
3837                 <br />
3838                 [% IF entry.cost_billed %]
3839                     Cost billed: [% entry.cost_billed %]
3840                     [% IF entry.billed_per_item %](per item)[% END %]
3841                     <br />
3842                 [% END %]
3843                 [% IF entry.actual_cost %]
3844                     Actual cost: [% entry.actual_cost %]<br />
3845                 [% END %]
3846                 [% IF entry.amount_paid %]
3847                     Amount paid: [% entry.amount_paid %]<br />
3848                 [% END %]
3849                 [% IF entry.note %]Note: [% entry.note %][% END %]
3850             </li>
3851         [% END %]
3852         [% FOR item IN invoice.items %]
3853             <li>
3854                 [% IF item.inv_item_type %]
3855                     Item Type: [% item.inv_item_type %]<br />
3856                 [% END %]
3857                 [% IF item.title %]Title/Description:
3858                     [% item.title %]<br />
3859                 [% END %]
3860                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3861                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3862                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3863                 [% IF item.cost_billed %]
3864                     Cost billed: [% item.cost_billed %]<br />
3865                 [% END %]
3866                 [% IF item.actual_cost %]
3867                     Actual cost: [% item.actual_cost %]<br />
3868                 [% END %]
3869                 [% IF item.amount_paid %]
3870                     Amount paid: [% item.amount_paid %]<br />
3871                 [% END %]
3872             </li>
3873         [% END %]
3874     </ul>
3875 </div>
3876 [% END %]
3877 $$
3878 );
3879
3880 INSERT INTO action_trigger.environment (event_def, path) VALUES
3881     (22, 'provider'),
3882     (22, 'provider.addresses'),
3883     (22, 'shipper'),
3884     (22, 'shipper.addresses'),
3885     (22, 'receiver'),
3886     (22, 'entries'),
3887     (22, 'entries.purchase_order'),
3888     (22, 'entries.lineitem'),
3889     (22, 'entries.lineitem.attributes'),
3890     (22, 'items')
3891 ;
3892
3893 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3894   (23, 'provider.edi_default');
3895
3896 INSERT INTO action_trigger.validator (module, description) 
3897     VALUES (
3898         'Acq::PurchaseOrderEDIRequired',
3899         oils_i18n_gettext(
3900             'Acq::PurchaseOrderEDIRequired',
3901             'Purchase order is delivered via EDI',
3902             'atval',
3903             'description'
3904         )
3905     );
3906
3907 INSERT INTO action_trigger.reactor (module, description)
3908     VALUES (
3909         'GeneratePurchaseOrderJEDI',
3910         oils_i18n_gettext(
3911             'GeneratePurchaseOrderJEDI',
3912             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
3913             'atreact',
3914             'description'
3915         )
3916     );
3917
3918 UPDATE action_trigger.hook 
3919     SET 
3920         key = 'acqpo.activated', 
3921         passive = FALSE,
3922         description = oils_i18n_gettext(
3923             'acqpo.activated',
3924             'Purchase order was activated',
3925             'ath',
3926             'description'
3927         )
3928     WHERE key = 'format.po.jedi';
3929
3930 -- We just changed a key in action_trigger.hook.  Now redirect any
3931 -- child rows to point to the new key.  (There probably aren't any;
3932 -- this is just a precaution against possible local modifications.)
3933
3934 UPDATE action_trigger.event_definition
3935 SET hook = 'acqpo.activated'
3936 WHERE hook = 'format.po.jedi';
3937
3938 INSERT INTO action_trigger.reactor (module, description) VALUES (
3939     'AstCall', 'Possibly place a phone call with Asterisk'
3940 );
3941
3942 INSERT INTO
3943     action_trigger.event_definition (
3944         id, active, owner, name, hook, validator, reactor,
3945         cleanup_success, cleanup_failure, delay, delay_field, group_field,
3946         max_delay, granularity, usr_field, opt_in_setting, template
3947     ) VALUES (
3948         24,
3949         FALSE,
3950         1,
3951         'Telephone Overdue Notice',
3952         'checkout.due', 'NOOP_True', 'AstCall',
3953         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
3954         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
3955         $$
3956 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
3957 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
3958 Channel: [% channel_prefix %]/[% country %][% phone %]
3959 Context: overdue-test
3960 MaxRetries: 1
3961 RetryTime: 60
3962 WaitTime: 30
3963 Extension: 10
3964 Archive: 1
3965 Set: eg_user_id=[% target.0.usr.id %]
3966 Set: items=[% target.size %]
3967 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
3968 $$
3969     );
3970
3971 INSERT INTO
3972     action_trigger.environment (id, event_def, path)
3973     VALUES
3974         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
3975         (DEFAULT, 24, 'usr')
3976     ;
3977
3978 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3979         'circ.format.history.email',
3980         'circ', 
3981         oils_i18n_gettext(
3982             'circ.format.history.email',
3983             'An email has been requested for a circ history.',
3984             'ath',
3985             'description'
3986         ), 
3987         FALSE
3988     )
3989     ,(
3990         'circ.format.history.print',
3991         'circ', 
3992         oils_i18n_gettext(
3993             'circ.format.history.print',
3994             'A circ history needs to be formatted for printing.',
3995             'ath',
3996             'description'
3997         ), 
3998         FALSE
3999     )
4000     ,(
4001         'ahr.format.history.email',
4002         'ahr', 
4003         oils_i18n_gettext(
4004             'ahr.format.history.email',
4005             'An email has been requested for a hold request history.',
4006             'ath',
4007             'description'
4008         ), 
4009         FALSE
4010     )
4011     ,(
4012         'ahr.format.history.print',
4013         'ahr', 
4014         oils_i18n_gettext(
4015             'ahr.format.history.print',
4016             'A hold request history needs to be formatted for printing.',
4017             'ath',
4018             'description'
4019         ), 
4020         FALSE
4021     )
4022
4023 ;
4024
4025 INSERT INTO action_trigger.event_definition (
4026         id,
4027         active,
4028         owner,
4029         name,
4030         hook,
4031         validator,
4032         reactor,
4033         group_field,
4034         granularity,
4035         template
4036     ) VALUES (
4037         25,
4038         TRUE,
4039         1,
4040         'circ.history.email',
4041         'circ.format.history.email',
4042         'NOOP_True',
4043         'SendEmail',
4044         'usr',
4045         NULL,
4046 $$
4047 [%- USE date -%]
4048 [%- SET user = target.0.usr -%]
4049 To: [%- params.recipient_email || user.email %]
4050 From: [%- params.sender_email || default_sender %]
4051 Subject: Circulation History
4052
4053     [% FOR circ IN target %]
4054             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4055             Barcode: [% circ.target_copy.barcode %]
4056             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
4057             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
4058             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
4059     [% END %]
4060 $$
4061     )
4062     ,(
4063         26,
4064         TRUE,
4065         1,
4066         'circ.history.print',
4067         'circ.format.history.print',
4068         'NOOP_True',
4069         'ProcessTemplate',
4070         'usr',
4071         'print-on-demand',
4072 $$
4073 [%- USE date -%]
4074 <div>
4075     <style> li { padding: 8px; margin 5px; }</style>
4076     <div>[% date.format %]</div>
4077     <br/>
4078
4079     [% user.family_name %], [% user.first_given_name %]
4080     <ol>
4081     [% FOR circ IN target %]
4082         <li>
4083             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
4084             <div>Barcode: [% circ.target_copy.barcode %]</div>
4085             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
4086             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
4087             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
4088         </li>
4089     [% END %]
4090     </ol>
4091 </div>
4092 $$
4093     )
4094     ,(
4095         27,
4096         TRUE,
4097         1,
4098         'ahr.history.email',
4099         'ahr.format.history.email',
4100         'NOOP_True',
4101         'SendEmail',
4102         'usr',
4103         NULL,
4104 $$
4105 [%- USE date -%]
4106 [%- SET user = target.0.usr -%]
4107 To: [%- params.recipient_email || user.email %]
4108 From: [%- params.sender_email || default_sender %]
4109 Subject: Hold Request History
4110
4111     [% FOR hold IN target %]
4112             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
4113             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
4114             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
4115     [% END %]
4116 $$
4117     )
4118     ,(
4119         28,
4120         TRUE,
4121         1,
4122         'ahr.history.print',
4123         'ahr.format.history.print',
4124         'NOOP_True',
4125         'ProcessTemplate',
4126         'usr',
4127         'print-on-demand',
4128 $$
4129 [%- USE date -%]
4130 <div>
4131     <style> li { padding: 8px; margin 5px; }</style>
4132     <div>[% date.format %]</div>
4133     <br/>
4134
4135     [% user.family_name %], [% user.first_given_name %]
4136     <ol>
4137     [% FOR hold IN target %]
4138         <li>
4139             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
4140             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
4141             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
4142         </li>
4143     [% END %]
4144     </ol>
4145 </div>
4146 $$
4147     )
4148
4149 ;
4150
4151 INSERT INTO action_trigger.environment (
4152         event_def,
4153         path
4154     ) VALUES 
4155          ( 25, 'target_copy')
4156         ,( 25, 'usr' )
4157         ,( 26, 'target_copy' )
4158         ,( 26, 'usr' )
4159         ,( 27, 'current_copy' )
4160         ,( 27, 'usr' )
4161         ,( 28, 'current_copy' )
4162         ,( 28, 'usr' )
4163 ;
4164
4165 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4166         'money.format.payment_receipt.email',
4167         'mp', 
4168         oils_i18n_gettext(
4169             'money.format.payment_receipt.email',
4170             'An email has been requested for a payment receipt.',
4171             'ath',
4172             'description'
4173         ), 
4174         FALSE
4175     )
4176     ,(
4177         'money.format.payment_receipt.print',
4178         'mp', 
4179         oils_i18n_gettext(
4180             'money.format.payment_receipt.print',
4181             'A payment receipt needs to be formatted for printing.',
4182             'ath',
4183             'description'
4184         ), 
4185         FALSE
4186     )
4187 ;
4188
4189 INSERT INTO action_trigger.event_definition (
4190         id,
4191         active,
4192         owner,
4193         name,
4194         hook,
4195         validator,
4196         reactor,
4197         group_field,
4198         granularity,
4199         template
4200     ) VALUES (
4201         29,
4202         TRUE,
4203         1,
4204         'money.payment_receipt.email',
4205         'money.format.payment_receipt.email',
4206         'NOOP_True',
4207         'SendEmail',
4208         'xact.usr',
4209         NULL,
4210 $$
4211 [%- USE date -%]
4212 [%- SET user = target.0.xact.usr -%]
4213 To: [%- params.recipient_email || user.email %]
4214 From: [%- params.sender_email || default_sender %]
4215 Subject: Payment Receipt
4216
4217 [% date.format -%]
4218 [%- SET xact_mp_hash = {} -%]
4219 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
4220     [%- SET xact_id = mp.xact.id -%]
4221     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
4222     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
4223 [%- END -%]
4224 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
4225     [%- SET xact = xact_mp_hash.$xact_id.xact %]
4226 Transaction ID: [% xact_id %]
4227     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4228     [% ELSE %]Miscellaneous
4229     [% END %]
4230     Line item billings:
4231         [%- SET mb_type_hash = {} -%]
4232         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
4233             [%- IF mb.voided == 'f' -%]
4234                 [%- SET mb_type = mb.btype.id -%]
4235                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
4236                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
4237                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
4238                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
4239                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
4240             [%- END -%]
4241         [%- END -%]
4242         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
4243             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
4244                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4245                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4246             [%- ELSE -%][%# all other billings show individually %]
4247                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
4248                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4249                 [% END %]
4250             [% END %]
4251         [% END %]
4252     Line item payments:
4253         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4254             Payment ID: [% mp.id %]
4255                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4256                     [% CASE "cash_payment" %]cash
4257                     [% CASE "check_payment" %]check
4258                     [% CASE "credit_card_payment" %]credit card (
4259                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4260                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4261                         [% cc_chunks.last -%]
4262                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4263                     )
4264                     [% CASE "credit_payment" %]credit
4265                     [% CASE "forgive_payment" %]forgiveness
4266                     [% CASE "goods_payment" %]goods
4267                     [% CASE "work_payment" %]work
4268                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
4269         [% END %]
4270 [% END %]
4271 $$
4272     )
4273     ,(
4274         30,
4275         TRUE,
4276         1,
4277         'money.payment_receipt.print',
4278         'money.format.payment_receipt.print',
4279         'NOOP_True',
4280         'ProcessTemplate',
4281         'xact.usr',
4282         'print-on-demand',
4283 $$
4284 [%- USE date -%][%- SET user = target.0.xact.usr -%]
4285 <div style="li { padding: 8px; margin 5px; }">
4286     <div>[% date.format %]</div><br/>
4287     <ol>
4288     [% SET xact_mp_hash = {} %]
4289     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
4290         [% SET xact_id = mp.xact.id %]
4291         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
4292         [% xact_mp_hash.$xact_id.payments.push(mp) %]
4293     [% END %]
4294     [% FOR xact_id IN xact_mp_hash.keys.sort %]
4295         [% SET xact = xact_mp_hash.$xact_id.xact %]
4296         <li>Transaction ID: [% xact_id %]
4297             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4298             [% ELSE %]Miscellaneous
4299             [% END %]
4300             Line item billings:<ol>
4301                 [% SET mb_type_hash = {} %]
4302                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
4303                     [% IF mb.voided == 'f' %]
4304                         [% SET mb_type = mb.btype.id %]
4305                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
4306                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
4307                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
4308                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
4309                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
4310                     [% END %]
4311                 [% END %]
4312                 [% FOR mb_type IN mb_type_hash.keys.sort %]
4313                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
4314                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4315                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4316                     [% ELSE %][%# all other billings show individually %]
4317                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
4318                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4319                         [% END %]
4320                     [% END %]</li>
4321                 [% END %]
4322             </ol>
4323             Line item payments:<ol>
4324                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4325                     <li>Payment ID: [% mp.id %]
4326                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4327                             [% CASE "cash_payment" %]cash
4328                             [% CASE "check_payment" %]check
4329                             [% CASE "credit_card_payment" %]credit card (
4330                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4331                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4332                                 [% cc_chunks.last -%]
4333                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4334                             )
4335                             [% CASE "credit_payment" %]credit
4336                             [% CASE "forgive_payment" %]forgiveness
4337                             [% CASE "goods_payment" %]goods
4338                             [% CASE "work_payment" %]work
4339                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
4340                     </li>
4341                 [% END %]
4342             </ol>
4343         </li>
4344     [% END %]
4345     </ol>
4346 </div>
4347 $$
4348     )
4349 ;
4350
4351 INSERT INTO action_trigger.environment (
4352         event_def,
4353         path
4354     ) VALUES -- for fleshing mp objects
4355          ( 29, 'xact')
4356         ,( 29, 'xact.usr')
4357         ,( 29, 'xact.grocery' )
4358         ,( 29, 'xact.circulation' )
4359         ,( 29, 'xact.summary' )
4360         ,( 30, 'xact')
4361         ,( 30, 'xact.usr')
4362         ,( 30, 'xact.grocery' )
4363         ,( 30, 'xact.circulation' )
4364         ,( 30, 'xact.summary' )
4365 ;
4366
4367 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
4368     'DeleteTempBiblioBucket',
4369     oils_i18n_gettext(
4370         'DeleteTempBiblioBucket',
4371         'Deletes a cbreb object used as a target if it has a btype of "temp"',
4372         'atclean',
4373         'description'
4374     )
4375 );
4376
4377 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4378         'biblio.format.record_entry.email',
4379         'cbreb', 
4380         oils_i18n_gettext(
4381             'biblio.format.record_entry.email',
4382             'An email has been requested for one or more biblio record entries.',
4383             'ath',
4384             'description'
4385         ), 
4386         FALSE
4387     )
4388     ,(
4389         'biblio.format.record_entry.print',
4390         'cbreb', 
4391         oils_i18n_gettext(
4392             'biblio.format.record_entry.print',
4393             'One or more biblio record entries need to be formatted for printing.',
4394             'ath',
4395             'description'
4396         ), 
4397         FALSE
4398     )
4399 ;
4400
4401 INSERT INTO action_trigger.event_definition (
4402         id,
4403         active,
4404         owner,
4405         name,
4406         hook,
4407         validator,
4408         reactor,
4409         cleanup_success,
4410         cleanup_failure,
4411         group_field,
4412         granularity,
4413         template
4414     ) VALUES (
4415         31,
4416         TRUE,
4417         1,
4418         'biblio.record_entry.email',
4419         'biblio.format.record_entry.email',
4420         'NOOP_True',
4421         'SendEmail',
4422         'DeleteTempBiblioBucket',
4423         'DeleteTempBiblioBucket',
4424         'owner',
4425         NULL,
4426 $$
4427 [%- USE date -%]
4428 [%- SET user = target.0.owner -%]
4429 To: [%- params.recipient_email || user.email %]
4430 From: [%- params.sender_email || default_sender %]
4431 Subject: Bibliographic Records
4432
4433     [% FOR cbreb IN target %]
4434     [% FOR cbrebi IN cbreb.items %]
4435         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
4436         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
4437         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
4438         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4439
4440     [% END %]
4441     [% END %]
4442 $$
4443     )
4444     ,(
4445         32,
4446         TRUE,
4447         1,
4448         'biblio.record_entry.print',
4449         'biblio.format.record_entry.print',
4450         'NOOP_True',
4451         'ProcessTemplate',
4452         'DeleteTempBiblioBucket',
4453         'DeleteTempBiblioBucket',
4454         'owner',
4455         'print-on-demand',
4456 $$
4457 [%- USE date -%]
4458 <div>
4459     <style> li { padding: 8px; margin 5px; }</style>
4460     <ol>
4461     [% FOR cbreb IN target %]
4462     [% FOR cbrebi IN cbreb.items %]
4463         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
4464             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
4465             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
4466             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4467         </li>
4468     [% END %]
4469     [% END %]
4470     </ol>
4471 </div>
4472 $$
4473     )
4474 ;
4475
4476 INSERT INTO action_trigger.environment (
4477         event_def,
4478         path
4479     ) VALUES -- for fleshing cbreb objects
4480          ( 31, 'owner' )
4481         ,( 31, 'items' )
4482         ,( 31, 'items.target_biblio_record_entry' )
4483         ,( 31, 'items.target_biblio_record_entry.simple_record' )
4484         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
4485         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
4486         ,( 31, 'items.target_biblio_record_entry.notes' )
4487         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
4488         ,( 32, 'owner' )
4489         ,( 32, 'items' )
4490         ,( 32, 'items.target_biblio_record_entry' )
4491         ,( 32, 'items.target_biblio_record_entry.simple_record' )
4492         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
4493         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
4494         ,( 32, 'items.target_biblio_record_entry.notes' )
4495         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
4496 ;
4497
4498 INSERT INTO action_trigger.environment (
4499         event_def,
4500         path
4501     ) VALUES -- for fleshing mp objects
4502          ( 29, 'credit_card_payment')
4503         ,( 29, 'xact.billings')
4504         ,( 29, 'xact.billings.btype')
4505         ,( 30, 'credit_card_payment')
4506         ,( 30, 'xact.billings')
4507         ,( 30, 'xact.billings.btype')
4508 ;
4509
4510 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
4511     (   'circ.format.missing_pieces.slip.print',
4512         'circ', 
4513         oils_i18n_gettext(
4514             'circ.format.missing_pieces.slip.print',
4515             'A missing pieces slip needs to be formatted for printing.',
4516             'ath',
4517             'description'
4518         ), 
4519         FALSE
4520     )
4521     ,(  'circ.format.missing_pieces.letter.print',
4522         'circ', 
4523         oils_i18n_gettext(
4524             'circ.format.missing_pieces.letter.print',
4525             'A missing pieces patron letter needs to be formatted for printing.',
4526             'ath',
4527             'description'
4528         ), 
4529         FALSE
4530     )
4531 ;
4532
4533 INSERT INTO action_trigger.event_definition (
4534         id,
4535         active,
4536         owner,
4537         name,
4538         hook,
4539         validator,
4540         reactor,
4541         group_field,
4542         granularity,
4543         template
4544     ) VALUES (
4545         33,
4546         TRUE,
4547         1,
4548         'circ.missing_pieces.slip.print',
4549         'circ.format.missing_pieces.slip.print',
4550         'NOOP_True',
4551         'ProcessTemplate',
4552         'usr',
4553         'print-on-demand',
4554 $$
4555 [%- USE date -%]
4556 [%- SET user = target.0.usr -%]
4557 <div style="li { padding: 8px; margin 5px; }">
4558     <div>[% date.format %]</div><br/>
4559     Missing pieces for:
4560     <ol>
4561     [% FOR circ IN target %]
4562         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
4563             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4564         </li>
4565     [% END %]
4566     </ol>
4567 </div>
4568 $$
4569     )
4570     ,(
4571         34,
4572         TRUE,
4573         1,
4574         'circ.missing_pieces.letter.print',
4575         'circ.format.missing_pieces.letter.print',
4576         'NOOP_True',
4577         'ProcessTemplate',
4578         'usr',
4579         'print-on-demand',
4580 $$
4581 [%- USE date -%]
4582 [%- SET user = target.0.usr -%]
4583 [% date.format %]
4584 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
4585
4586 We are missing pieces for the following returned items:
4587 [% FOR circ IN target %]
4588 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
4589 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4590 [% END %]
4591
4592 Please return these pieces as soon as possible.
4593
4594 Thanks!
4595
4596 Library Staff
4597 $$
4598     )
4599 ;
4600
4601 INSERT INTO action_trigger.environment (
4602         event_def,
4603         path
4604     ) VALUES -- for fleshing circ objects
4605          ( 33, 'usr')
4606         ,( 33, 'target_copy')
4607         ,( 33, 'target_copy.circ_lib')
4608         ,( 33, 'target_copy.circ_lib.mailing_address')
4609         ,( 33, 'target_copy.circ_lib.billing_address')
4610         ,( 33, 'target_copy.call_number')
4611         ,( 33, 'target_copy.call_number.owning_lib')
4612         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
4613         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
4614         ,( 33, 'circ_lib')
4615         ,( 33, 'circ_lib.mailing_address')
4616         ,( 33, 'circ_lib.billing_address')
4617         ,( 34, 'usr')
4618         ,( 34, 'target_copy')
4619         ,( 34, 'target_copy.circ_lib')
4620         ,( 34, 'target_copy.circ_lib.mailing_address')
4621         ,( 34, 'target_copy.circ_lib.billing_address')
4622         ,( 34, 'target_copy.call_number')
4623         ,( 34, 'target_copy.call_number.owning_lib')
4624         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
4625         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
4626         ,( 34, 'circ_lib')
4627         ,( 34, 'circ_lib.mailing_address')
4628         ,( 34, 'circ_lib.billing_address')
4629 ;
4630
4631 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
4632     VALUES (   
4633         'ahr.format.pull_list',
4634         'ahr', 
4635         oils_i18n_gettext(
4636             'ahr.format.pull_list',
4637             'Format holds pull list for printing',
4638             'ath',
4639             'description'
4640         ), 
4641         FALSE
4642     );
4643
4644 INSERT INTO action_trigger.event_definition (
4645         id,
4646         active,
4647         owner,
4648         name,
4649         hook,
4650         validator,
4651         reactor,
4652         group_field,
4653         granularity,
4654         template
4655     ) VALUES (
4656         35,
4657         TRUE,
4658         1,
4659         'Holds Pull List',
4660         'ahr.format.pull_list',
4661         'NOOP_True',
4662         'ProcessTemplate',
4663         'pickup_lib',
4664         'print-on-demand',
4665 $$
4666 [%- USE date -%]
4667 <style>
4668     table { border-collapse: collapse; } 
4669     td { padding: 5px; border-bottom: 1px solid #888; } 
4670     th { font-weight: bold; }
4671 </style>
4672 [% 
4673     # Sort the holds into copy-location buckets
4674     # In the main print loop, sort each bucket by callnumber before printing
4675     SET holds_list = [];
4676     SET loc_data = [];
4677     SET current_location = target.0.current_copy.location.id;
4678     FOR hold IN target;
4679         IF current_location != hold.current_copy.location.id;
4680             SET current_location = hold.current_copy.location.id;
4681             holds_list.push(loc_data);
4682             SET loc_data = [];
4683         END;
4684         SET hold_data = {
4685             'hold' => hold,
4686             'callnumber' => hold.current_copy.call_number.label
4687         };
4688         loc_data.push(hold_data);
4689     END;
4690     holds_list.push(loc_data)
4691 %]
4692 <table>
4693     <thead>
4694         <tr>
4695             <th>Title</th>
4696             <th>Author</th>
4697             <th>Shelving Location</th>
4698             <th>Call Number</th>
4699             <th>Barcode</th>
4700             <th>Patron</th>
4701         </tr>
4702     </thead>
4703     <tbody>
4704     [% FOR loc_data IN holds_list  %]
4705         [% FOR hold_data IN loc_data.sort('callnumber') %]
4706             [% 
4707                 SET hold = hold_data.hold;
4708                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
4709             %]
4710             <tr>
4711                 <td>[% copy_data.title | truncate %]</td>
4712                 <td>[% copy_data.author | truncate %]</td>
4713                 <td>[% hold.current_copy.location.name %]</td>
4714                 <td>[% hold.current_copy.call_number.label %]</td>
4715                 <td>[% hold.current_copy.barcode %]</td>
4716                 <td>[% hold.usr.card.barcode %]</td>
4717             </tr>
4718         [% END %]
4719     [% END %]
4720     <tbody>
4721 </table>
4722 $$
4723 );
4724
4725 INSERT INTO action_trigger.environment (
4726         event_def,
4727         path
4728     ) VALUES
4729         (35, 'current_copy.location'),
4730         (35, 'current_copy.call_number'),
4731         (35, 'usr.card'),
4732         (35, 'pickup_lib')
4733 ;
4734
4735 INSERT INTO action_trigger.validator (module, description) VALUES ( 
4736     'HoldIsCancelled', 
4737     oils_i18n_gettext( 
4738         'HoldIsCancelled', 
4739         'Check whether a hold request is cancelled.', 
4740         'atval', 
4741         'description' 
4742     ) 
4743 );
4744
4745 -- Create the query schema, and the tables and views therein
4746
4747 DROP SCHEMA IF EXISTS sql CASCADE;
4748 DROP SCHEMA IF EXISTS query CASCADE;
4749
4750 CREATE SCHEMA query;
4751
4752 CREATE TABLE query.datatype (
4753         id              SERIAL            PRIMARY KEY,
4754         datatype_name   TEXT              NOT NULL UNIQUE,
4755         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
4756         is_composite    BOOL              NOT NULL DEFAULT FALSE,
4757         CONSTRAINT qdt_comp_not_num CHECK
4758         ( is_numeric IS FALSE OR is_composite IS FALSE )
4759 );
4760
4761 -- Define the most common datatypes in query.datatype.  Note that none of
4762 -- these stock datatypes specifies a width or precision.
4763
4764 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
4765 -- room for more stock datatypes if we ever want to add them.
4766
4767 SELECT setval( 'query.datatype_id_seq', 1000 );
4768
4769 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4770   VALUES (1, 'SMALLINT', true);
4771  
4772 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4773   VALUES (2, 'INTEGER', true);
4774  
4775 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4776   VALUES (3, 'BIGINT', true);
4777  
4778 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4779   VALUES (4, 'DECIMAL', true);
4780  
4781 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4782   VALUES (5, 'NUMERIC', true);
4783  
4784 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4785   VALUES (6, 'REAL', true);
4786  
4787 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4788   VALUES (7, 'DOUBLE PRECISION', true);
4789  
4790 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4791   VALUES (8, 'SERIAL', true);
4792  
4793 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4794   VALUES (9, 'BIGSERIAL', true);
4795  
4796 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4797   VALUES (10, 'MONEY', false);
4798  
4799 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4800   VALUES (11, 'VARCHAR', false);
4801  
4802 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4803   VALUES (12, 'CHAR', false);
4804  
4805 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4806   VALUES (13, 'TEXT', false);
4807  
4808 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4809   VALUES (14, '"char"', false);
4810  
4811 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4812   VALUES (15, 'NAME', false);
4813  
4814 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4815   VALUES (16, 'BYTEA', false);
4816  
4817 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4818   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
4819  
4820 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4821   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
4822  
4823 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4824   VALUES (19, 'DATE', false);
4825  
4826 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4827   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
4828  
4829 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4830   VALUES (21, 'TIME WITH TIME ZONE', false);
4831  
4832 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4833   VALUES (22, 'INTERVAL', false);
4834  
4835 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4836   VALUES (23, 'BOOLEAN', false);
4837  
4838 CREATE TABLE query.subfield (
4839         id              SERIAL            PRIMARY KEY,
4840         composite_type  INT               NOT NULL
4841                                           REFERENCES query.datatype(id)
4842                                           ON DELETE CASCADE
4843                                           DEFERRABLE INITIALLY DEFERRED,
4844         seq_no          INT               NOT NULL
4845                                           CONSTRAINT qsf_pos_seq_no
4846                                           CHECK( seq_no > 0 ),
4847         subfield_type   INT               NOT NULL
4848                                           REFERENCES query.datatype(id)
4849                                           DEFERRABLE INITIALLY DEFERRED,
4850         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
4851 );
4852
4853 CREATE TABLE query.function_sig (
4854         id              SERIAL            PRIMARY KEY,
4855         function_name   TEXT              NOT NULL,
4856         return_type     INT               REFERENCES query.datatype(id)
4857                                           DEFERRABLE INITIALLY DEFERRED,
4858         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
4859         CONSTRAINT qfd_rtn_or_aggr CHECK
4860         ( return_type IS NULL OR is_aggregate = FALSE )
4861 );
4862
4863 CREATE INDEX query_function_sig_name_idx 
4864         ON query.function_sig (function_name);
4865
4866 CREATE TABLE query.function_param_def (
4867         id              SERIAL            PRIMARY KEY,
4868         function_id     INT               NOT NULL
4869                                           REFERENCES query.function_sig( id )
4870                                           ON DELETE CASCADE
4871                                           DEFERRABLE INITIALLY DEFERRED,
4872         seq_no          INT               NOT NULL
4873                                           CONSTRAINT qfpd_pos_seq_no CHECK
4874                                           ( seq_no > 0 ),
4875         datatype        INT               NOT NULL
4876                                           REFERENCES query.datatype( id )
4877                                           DEFERRABLE INITIALLY DEFERRED,
4878         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
4879 );
4880
4881 CREATE TABLE  query.stored_query (
4882         id            SERIAL         PRIMARY KEY,
4883         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
4884                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
4885         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
4886         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
4887         from_clause   INT            , --REFERENCES query.from_clause
4888                                      --DEFERRABLE INITIALLY DEFERRED,
4889         where_clause  INT            , --REFERENCES query.expression
4890                                      --DEFERRABLE INITIALLY DEFERRED,
4891         having_clause INT            , --REFERENCES query.expression
4892                                      --DEFERRABLE INITIALLY DEFERRED
4893         limit_count   INT            , --REFERENCES query.expression( id )
4894                                      --DEFERRABLE INITIALLY DEFERRED,
4895         offset_count  INT            --REFERENCES query.expression( id )
4896                                      --DEFERRABLE INITIALLY DEFERRED
4897 );
4898
4899 -- (Foreign keys to be defined later after other tables are created)
4900
4901 CREATE TABLE query.query_sequence (
4902         id              SERIAL            PRIMARY KEY,
4903         parent_query    INT               NOT NULL
4904                                           REFERENCES query.stored_query
4905                                                                           ON DELETE CASCADE
4906                                                                           DEFERRABLE INITIALLY DEFERRED,
4907         seq_no          INT               NOT NULL,
4908         child_query     INT               NOT NULL
4909                                           REFERENCES query.stored_query
4910                                                                           ON DELETE CASCADE
4911                                                                           DEFERRABLE INITIALLY DEFERRED,
4912         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
4913 );
4914
4915 CREATE TABLE query.bind_variable (
4916         name          TEXT             PRIMARY KEY,
4917         type          TEXT             NOT NULL
4918                                            CONSTRAINT bind_variable_type CHECK
4919                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
4920         description   TEXT             NOT NULL,
4921         default_value TEXT,            -- to be encoded in JSON
4922         label         TEXT             NOT NULL
4923 );
4924
4925 CREATE TABLE query.expression (
4926         id            SERIAL        PRIMARY KEY,
4927         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
4928                                     ( type IN (
4929                                     'xbet',    -- between
4930                                     'xbind',   -- bind variable
4931                                     'xbool',   -- boolean
4932                                     'xcase',   -- case
4933                                     'xcast',   -- cast
4934                                     'xcol',    -- column
4935                                     'xex',     -- exists
4936                                     'xfunc',   -- function
4937                                     'xin',     -- in
4938                                     'xisnull', -- is null
4939                                     'xnull',   -- null
4940                                     'xnum',    -- number
4941                                     'xop',     -- operator
4942                                     'xser',    -- series
4943                                     'xstr',    -- string
4944                                     'xsubq'    -- subquery
4945                                                                 ) ),
4946         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
4947         parent_expr   INT           REFERENCES query.expression
4948                                     ON DELETE CASCADE
4949                                     DEFERRABLE INITIALLY DEFERRED,
4950         seq_no        INT           NOT NULL DEFAULT 1,
4951         literal       TEXT,
4952         table_alias   TEXT,
4953         column_name   TEXT,
4954         left_operand  INT           REFERENCES query.expression
4955                                     DEFERRABLE INITIALLY DEFERRED,
4956         operator      TEXT,
4957         right_operand INT           REFERENCES query.expression
4958                                     DEFERRABLE INITIALLY DEFERRED,
4959         function_id   INT           REFERENCES query.function_sig
4960                                     DEFERRABLE INITIALLY DEFERRED,
4961         subquery      INT           REFERENCES query.stored_query
4962                                     DEFERRABLE INITIALLY DEFERRED,
4963         cast_type     INT           REFERENCES query.datatype
4964                                     DEFERRABLE INITIALLY DEFERRED,
4965         negate        BOOL          NOT NULL DEFAULT FALSE,
4966         bind_variable TEXT          REFERENCES query.bind_variable
4967                                         DEFERRABLE INITIALLY DEFERRED
4968 );
4969
4970 CREATE UNIQUE INDEX query_expr_parent_seq
4971         ON query.expression( parent_expr, seq_no )
4972         WHERE parent_expr IS NOT NULL;
4973
4974 -- Due to some circular references, the following foreign key definitions
4975 -- had to be deferred until query.expression existed:
4976
4977 ALTER TABLE query.stored_query
4978         ADD FOREIGN KEY ( where_clause )
4979         REFERENCES query.expression( id )
4980         DEFERRABLE INITIALLY DEFERRED;
4981
4982 ALTER TABLE query.stored_query
4983         ADD FOREIGN KEY ( having_clause )
4984         REFERENCES query.expression( id )
4985         DEFERRABLE INITIALLY DEFERRED;
4986
4987 ALTER TABLE query.stored_query
4988     ADD FOREIGN KEY ( limit_count )
4989     REFERENCES query.expression( id )
4990     DEFERRABLE INITIALLY DEFERRED;
4991
4992 ALTER TABLE query.stored_query
4993     ADD FOREIGN KEY ( offset_count )
4994     REFERENCES query.expression( id )
4995     DEFERRABLE INITIALLY DEFERRED;
4996
4997 CREATE TABLE query.case_branch (
4998         id            SERIAL        PRIMARY KEY,
4999         parent_expr   INT           NOT NULL REFERENCES query.expression
5000                                     ON DELETE CASCADE
5001                                     DEFERRABLE INITIALLY DEFERRED,
5002         seq_no        INT           NOT NULL,
5003         condition     INT           REFERENCES query.expression
5004                                     DEFERRABLE INITIALLY DEFERRED,
5005         result        INT           NOT NULL REFERENCES query.expression
5006                                     DEFERRABLE INITIALLY DEFERRED,
5007         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
5008 );
5009
5010 CREATE TABLE query.from_relation (
5011         id               SERIAL        PRIMARY KEY,
5012         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
5013                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
5014         table_name       TEXT,
5015         class_name       TEXT,
5016         subquery         INT           REFERENCES query.stored_query,
5017         function_call    INT           REFERENCES query.expression,
5018         table_alias      TEXT,
5019         parent_relation  INT           REFERENCES query.from_relation
5020                                        ON DELETE CASCADE
5021                                        DEFERRABLE INITIALLY DEFERRED,
5022         seq_no           INT           NOT NULL DEFAULT 1,
5023         join_type        TEXT          CONSTRAINT good_join_type CHECK (
5024                                            join_type IS NULL OR join_type IN
5025                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
5026                                        ),
5027         on_clause        INT           REFERENCES query.expression
5028                                        DEFERRABLE INITIALLY DEFERRED,
5029         CONSTRAINT join_or_core CHECK (
5030         ( parent_relation IS NULL AND join_type IS NULL
5031           AND on_clause IS NULL )
5032         OR
5033         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
5034           AND on_clause IS NOT NULL )
5035         )
5036 );
5037
5038 CREATE UNIQUE INDEX from_parent_seq
5039         ON query.from_relation( parent_relation, seq_no )
5040         WHERE parent_relation IS NOT NULL;
5041
5042 -- The following foreign key had to be deferred until
5043 -- query.from_relation existed
5044
5045 ALTER TABLE query.stored_query
5046         ADD FOREIGN KEY (from_clause)
5047         REFERENCES query.from_relation
5048         DEFERRABLE INITIALLY DEFERRED;
5049
5050 CREATE TABLE query.record_column (
5051         id            SERIAL            PRIMARY KEY,
5052         from_relation INT               NOT NULL REFERENCES query.from_relation
5053                                         ON DELETE CASCADE
5054                                         DEFERRABLE INITIALLY DEFERRED,
5055         seq_no        INT               NOT NULL,
5056         column_name   TEXT              NOT NULL,
5057         column_type   INT               NOT NULL REFERENCES query.datatype
5058                                         ON DELETE CASCADE
5059                                                                         DEFERRABLE INITIALLY DEFERRED,
5060         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
5061 );
5062
5063 CREATE TABLE query.select_item (
5064         id               SERIAL         PRIMARY KEY,
5065         stored_query     INT            NOT NULL REFERENCES query.stored_query
5066                                         ON DELETE CASCADE
5067                                         DEFERRABLE INITIALLY DEFERRED,
5068         seq_no           INT            NOT NULL,
5069         expression       INT            NOT NULL REFERENCES query.expression
5070                                         DEFERRABLE INITIALLY DEFERRED,
5071         column_alias     TEXT,
5072         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
5073         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
5074 );
5075
5076 CREATE TABLE query.order_by_item (
5077         id               SERIAL         PRIMARY KEY,
5078         stored_query     INT            NOT NULL REFERENCES query.stored_query
5079                                         ON DELETE CASCADE
5080                                         DEFERRABLE INITIALLY DEFERRED,
5081         seq_no           INT            NOT NULL,
5082         expression       INT            NOT NULL REFERENCES query.expression
5083                                         ON DELETE CASCADE
5084                                         DEFERRABLE INITIALLY DEFERRED,
5085         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
5086 );
5087
5088 ------------------------------------------------------------
5089 -- Create updatable views for different kinds of expressions
5090 ------------------------------------------------------------
5091
5092 -- Create updatable view for BETWEEN expressions
5093
5094 CREATE OR REPLACE VIEW query.expr_xbet AS
5095     SELECT
5096                 id,
5097                 parenthesize,
5098                 parent_expr,
5099                 seq_no,
5100                 left_operand,
5101                 negate
5102     FROM
5103         query.expression
5104     WHERE
5105         type = 'xbet';
5106
5107 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
5108     ON INSERT TO query.expr_xbet
5109     DO INSTEAD
5110     INSERT INTO query.expression (
5111                 id,
5112                 type,
5113                 parenthesize,
5114                 parent_expr,
5115                 seq_no,
5116                 left_operand,
5117                 negate
5118     ) VALUES (
5119         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5120         'xbet',
5121         COALESCE(NEW.parenthesize, FALSE),
5122         NEW.parent_expr,
5123         COALESCE(NEW.seq_no, 1),
5124                 NEW.left_operand,
5125                 COALESCE(NEW.negate, false)
5126     );
5127
5128 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
5129     ON UPDATE TO query.expr_xbet
5130     DO INSTEAD
5131     UPDATE query.expression SET
5132         id = NEW.id,
5133         parenthesize = NEW.parenthesize,
5134         parent_expr = NEW.parent_expr,
5135         seq_no = NEW.seq_no,
5136                 left_operand = NEW.left_operand,
5137                 negate = NEW.negate
5138     WHERE
5139         id = OLD.id;
5140
5141 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
5142     ON DELETE TO query.expr_xbet
5143     DO INSTEAD
5144     DELETE FROM query.expression WHERE id = OLD.id;
5145
5146 -- Create updatable view for bind variable expressions
5147
5148 CREATE OR REPLACE VIEW query.expr_xbind AS
5149     SELECT
5150                 id,
5151                 parenthesize,
5152                 parent_expr,
5153                 seq_no,
5154                 bind_variable
5155     FROM
5156         query.expression
5157     WHERE
5158         type = 'xbind';
5159
5160 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
5161     ON INSERT TO query.expr_xbind
5162     DO INSTEAD
5163     INSERT INTO query.expression (
5164                 id,
5165                 type,
5166                 parenthesize,
5167                 parent_expr,
5168                 seq_no,
5169                 bind_variable
5170     ) VALUES (
5171         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5172         'xbind',
5173         COALESCE(NEW.parenthesize, FALSE),
5174         NEW.parent_expr,
5175         COALESCE(NEW.seq_no, 1),
5176                 NEW.bind_variable
5177     );
5178
5179 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
5180     ON UPDATE TO query.expr_xbind
5181     DO INSTEAD
5182     UPDATE query.expression SET
5183         id = NEW.id,
5184         parenthesize = NEW.parenthesize,
5185         parent_expr = NEW.parent_expr,
5186         seq_no = NEW.seq_no,
5187                 bind_variable = NEW.bind_variable
5188     WHERE
5189         id = OLD.id;
5190
5191 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
5192     ON DELETE TO query.expr_xbind
5193     DO INSTEAD
5194     DELETE FROM query.expression WHERE id = OLD.id;
5195
5196 -- Create updatable view for boolean expressions
5197
5198 CREATE OR REPLACE VIEW query.expr_xbool AS
5199     SELECT
5200                 id,
5201                 parenthesize,
5202                 parent_expr,
5203                 seq_no,
5204                 literal,
5205                 negate
5206     FROM
5207         query.expression
5208     WHERE
5209         type = 'xbool';
5210
5211 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
5212     ON INSERT TO query.expr_xbool
5213     DO INSTEAD
5214     INSERT INTO query.expression (
5215                 id,
5216                 type,
5217                 parenthesize,
5218                 parent_expr,
5219                 seq_no,
5220                 literal,
5221                 negate
5222     ) VALUES (
5223         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5224         'xbool',
5225         COALESCE(NEW.parenthesize, FALSE),
5226         NEW.parent_expr,
5227         COALESCE(NEW.seq_no, 1),
5228         NEW.literal,
5229                 COALESCE(NEW.negate, false)
5230     );
5231
5232 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
5233     ON UPDATE TO query.expr_xbool
5234     DO INSTEAD
5235     UPDATE query.expression SET
5236         id = NEW.id,
5237         parenthesize = NEW.parenthesize,
5238         parent_expr = NEW.parent_expr,
5239         seq_no = NEW.seq_no,
5240         literal = NEW.literal,
5241                 negate = NEW.negate
5242     WHERE
5243         id = OLD.id;
5244
5245 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
5246     ON DELETE TO query.expr_xbool
5247     DO INSTEAD
5248     DELETE FROM query.expression WHERE id = OLD.id;
5249
5250 -- Create updatable view for CASE expressions
5251
5252 CREATE OR REPLACE VIEW query.expr_xcase AS
5253     SELECT
5254                 id,
5255                 parenthesize,
5256                 parent_expr,
5257                 seq_no,
5258                 left_operand,
5259                 negate
5260     FROM
5261         query.expression
5262     WHERE
5263         type = 'xcase';
5264
5265 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
5266     ON INSERT TO query.expr_xcase
5267     DO INSTEAD
5268     INSERT INTO query.expression (
5269                 id,
5270                 type,
5271                 parenthesize,
5272                 parent_expr,
5273                 seq_no,
5274                 left_operand,
5275                 negate
5276     ) VALUES (
5277         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5278         'xcase',
5279         COALESCE(NEW.parenthesize, FALSE),
5280         NEW.parent_expr,
5281         COALESCE(NEW.seq_no, 1),
5282                 NEW.left_operand,
5283                 COALESCE(NEW.negate, false)
5284     );
5285
5286 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
5287     ON UPDATE TO query.expr_xcase
5288     DO INSTEAD
5289     UPDATE query.expression SET
5290         id = NEW.id,
5291         parenthesize = NEW.parenthesize,
5292         parent_expr = NEW.parent_expr,
5293         seq_no = NEW.seq_no,
5294                 left_operand = NEW.left_operand,
5295                 negate = NEW.negate
5296     WHERE
5297         id = OLD.id;
5298
5299 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
5300     ON DELETE TO query.expr_xcase
5301     DO INSTEAD
5302     DELETE FROM query.expression WHERE id = OLD.id;
5303
5304 -- Create updatable view for cast expressions
5305
5306 CREATE OR REPLACE VIEW query.expr_xcast AS
5307     SELECT
5308                 id,
5309                 parenthesize,
5310                 parent_expr,
5311                 seq_no,
5312                 left_operand,
5313                 cast_type,
5314                 negate
5315     FROM
5316         query.expression
5317     WHERE
5318         type = 'xcast';
5319
5320 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
5321     ON INSERT TO query.expr_xcast
5322     DO INSTEAD
5323     INSERT INTO query.expression (
5324         id,
5325         type,
5326         parenthesize,
5327         parent_expr,
5328         seq_no,
5329         left_operand,
5330         cast_type,
5331         negate
5332     ) VALUES (
5333         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5334         'xcast',
5335         COALESCE(NEW.parenthesize, FALSE),
5336         NEW.parent_expr,
5337         COALESCE(NEW.seq_no, 1),
5338         NEW.left_operand,
5339         NEW.cast_type,
5340         COALESCE(NEW.negate, false)
5341     );
5342
5343 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
5344     ON UPDATE TO query.expr_xcast
5345     DO INSTEAD
5346     UPDATE query.expression SET
5347         id = NEW.id,
5348         parenthesize = NEW.parenthesize,
5349         parent_expr = NEW.parent_expr,
5350         seq_no = NEW.seq_no,
5351                 left_operand = NEW.left_operand,
5352                 cast_type = NEW.cast_type,
5353                 negate = NEW.negate
5354     WHERE
5355         id = OLD.id;
5356
5357 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
5358     ON DELETE TO query.expr_xcast
5359     DO INSTEAD
5360     DELETE FROM query.expression WHERE id = OLD.id;
5361
5362 -- Create updatable view for column expressions
5363
5364 CREATE OR REPLACE VIEW query.expr_xcol AS
5365     SELECT
5366                 id,
5367                 parenthesize,
5368                 parent_expr,
5369                 seq_no,
5370                 table_alias,
5371                 column_name,
5372                 negate
5373     FROM
5374         query.expression
5375     WHERE
5376         type = 'xcol';
5377
5378 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
5379     ON INSERT TO query.expr_xcol
5380     DO INSTEAD
5381     INSERT INTO query.expression (
5382                 id,
5383                 type,
5384                 parenthesize,
5385                 parent_expr,
5386                 seq_no,
5387                 table_alias,
5388                 column_name,
5389                 negate
5390     ) VALUES (
5391         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5392         'xcol',
5393         COALESCE(NEW.parenthesize, FALSE),
5394         NEW.parent_expr,
5395         COALESCE(NEW.seq_no, 1),
5396                 NEW.table_alias,
5397                 NEW.column_name,
5398                 COALESCE(NEW.negate, false)
5399     );
5400
5401 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
5402     ON UPDATE TO query.expr_xcol
5403     DO INSTEAD
5404     UPDATE query.expression SET
5405         id = NEW.id,
5406         parenthesize = NEW.parenthesize,
5407         parent_expr = NEW.parent_expr,
5408         seq_no = NEW.seq_no,
5409                 table_alias = NEW.table_alias,
5410                 column_name = NEW.column_name,
5411                 negate = NEW.negate
5412     WHERE
5413         id = OLD.id;
5414
5415 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
5416     ON DELETE TO query.expr_xcol
5417     DO INSTEAD
5418     DELETE FROM query.expression WHERE id = OLD.id;
5419
5420 -- Create updatable view for EXISTS expressions
5421
5422 CREATE OR REPLACE VIEW query.expr_xex AS
5423     SELECT
5424                 id,
5425                 parenthesize,
5426                 parent_expr,
5427                 seq_no,
5428                 subquery,
5429                 negate
5430     FROM
5431         query.expression
5432     WHERE
5433         type = 'xex';
5434
5435 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
5436     ON INSERT TO query.expr_xex
5437     DO INSTEAD
5438     INSERT INTO query.expression (
5439                 id,
5440                 type,
5441                 parenthesize,
5442                 parent_expr,
5443                 seq_no,
5444                 subquery,
5445                 negate
5446     ) VALUES (
5447         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5448         'xex',
5449         COALESCE(NEW.parenthesize, FALSE),
5450         NEW.parent_expr,
5451         COALESCE(NEW.seq_no, 1),
5452                 NEW.subquery,
5453                 COALESCE(NEW.negate, false)
5454     );
5455
5456 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
5457     ON UPDATE TO query.expr_xex
5458     DO INSTEAD
5459     UPDATE query.expression SET
5460         id = NEW.id,
5461         parenthesize = NEW.parenthesize,
5462         parent_expr = NEW.parent_expr,
5463         seq_no = NEW.seq_no,
5464                 subquery = NEW.subquery,
5465                 negate = NEW.negate
5466     WHERE
5467         id = OLD.id;
5468
5469 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
5470     ON DELETE TO query.expr_xex
5471     DO INSTEAD
5472     DELETE FROM query.expression WHERE id = OLD.id;
5473
5474 -- Create updatable view for function call expressions
5475
5476 CREATE OR REPLACE VIEW query.expr_xfunc AS
5477     SELECT
5478         id,
5479         parenthesize,
5480         parent_expr,
5481         seq_no,
5482         column_name,
5483         function_id,
5484         negate
5485     FROM
5486         query.expression
5487     WHERE
5488         type = 'xfunc';
5489
5490 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
5491     ON INSERT TO query.expr_xfunc
5492     DO INSTEAD
5493     INSERT INTO query.expression (
5494         id,
5495         type,
5496         parenthesize,
5497         parent_expr,
5498         seq_no,
5499         column_name,
5500         function_id,
5501         negate
5502     ) VALUES (
5503         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5504         'xfunc',
5505         COALESCE(NEW.parenthesize, FALSE),
5506         NEW.parent_expr,
5507         COALESCE(NEW.seq_no, 1),
5508         NEW.column_name,
5509         NEW.function_id,
5510         COALESCE(NEW.negate, false)
5511     );
5512
5513 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
5514     ON UPDATE TO query.expr_xfunc
5515     DO INSTEAD
5516     UPDATE query.expression SET
5517         id = NEW.id,
5518         parenthesize = NEW.parenthesize,
5519         parent_expr = NEW.parent_expr,
5520         seq_no = NEW.seq_no,
5521         column_name = NEW.column_name,
5522         function_id = NEW.function_id,
5523         negate = NEW.negate
5524     WHERE
5525         id = OLD.id;
5526
5527 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
5528     ON DELETE TO query.expr_xfunc
5529     DO INSTEAD
5530     DELETE FROM query.expression WHERE id = OLD.id;
5531
5532 -- Create updatable view for IN expressions
5533
5534 CREATE OR REPLACE VIEW query.expr_xin AS
5535     SELECT
5536                 id,
5537                 parenthesize,
5538                 parent_expr,
5539                 seq_no,
5540                 left_operand,
5541                 subquery,
5542                 negate
5543     FROM
5544         query.expression
5545     WHERE
5546         type = 'xin';
5547
5548 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
5549     ON INSERT TO query.expr_xin
5550     DO INSTEAD
5551     INSERT INTO query.expression (
5552                 id,
5553                 type,
5554                 parenthesize,
5555                 parent_expr,
5556                 seq_no,
5557                 left_operand,
5558                 subquery,
5559                 negate
5560     ) VALUES (
5561         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5562         'xin',
5563         COALESCE(NEW.parenthesize, FALSE),
5564         NEW.parent_expr,
5565         COALESCE(NEW.seq_no, 1),
5566                 NEW.left_operand,
5567                 NEW.subquery,
5568                 COALESCE(NEW.negate, false)
5569     );
5570
5571 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
5572     ON UPDATE TO query.expr_xin
5573     DO INSTEAD
5574     UPDATE query.expression SET
5575         id = NEW.id,
5576         parenthesize = NEW.parenthesize,
5577         parent_expr = NEW.parent_expr,
5578         seq_no = NEW.seq_no,
5579                 left_operand = NEW.left_operand,
5580                 subquery = NEW.subquery,
5581                 negate = NEW.negate
5582     WHERE
5583         id = OLD.id;
5584
5585 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
5586     ON DELETE TO query.expr_xin
5587     DO INSTEAD
5588     DELETE FROM query.expression WHERE id = OLD.id;
5589
5590 -- Create updatable view for IS NULL expressions
5591
5592 CREATE OR REPLACE VIEW query.expr_xisnull AS
5593     SELECT
5594                 id,
5595                 parenthesize,
5596                 parent_expr,
5597                 seq_no,
5598                 left_operand,
5599                 negate
5600     FROM
5601         query.expression
5602     WHERE
5603         type = 'xisnull';
5604
5605 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
5606     ON INSERT TO query.expr_xisnull
5607     DO INSTEAD
5608     INSERT INTO query.expression (
5609                 id,
5610                 type,
5611                 parenthesize,
5612                 parent_expr,
5613                 seq_no,
5614                 left_operand,
5615                 negate
5616     ) VALUES (
5617         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5618         'xisnull',
5619         COALESCE(NEW.parenthesize, FALSE),
5620         NEW.parent_expr,
5621         COALESCE(NEW.seq_no, 1),
5622                 NEW.left_operand,
5623                 COALESCE(NEW.negate, false)
5624     );
5625
5626 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
5627     ON UPDATE TO query.expr_xisnull
5628     DO INSTEAD
5629     UPDATE query.expression SET
5630         id = NEW.id,
5631         parenthesize = NEW.parenthesize,
5632         parent_expr = NEW.parent_expr,
5633         seq_no = NEW.seq_no,
5634                 left_operand = NEW.left_operand,
5635                 negate = NEW.negate
5636     WHERE
5637         id = OLD.id;
5638
5639 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
5640     ON DELETE TO query.expr_xisnull
5641     DO INSTEAD
5642     DELETE FROM query.expression WHERE id = OLD.id;
5643
5644 -- Create updatable view for NULL expressions
5645
5646 CREATE OR REPLACE VIEW query.expr_xnull AS
5647     SELECT
5648                 id,
5649                 parenthesize,
5650                 parent_expr,
5651                 seq_no,
5652                 negate
5653     FROM
5654         query.expression
5655     WHERE
5656         type = 'xnull';
5657
5658 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
5659     ON INSERT TO query.expr_xnull
5660     DO INSTEAD
5661     INSERT INTO query.expression (
5662                 id,
5663                 type,
5664                 parenthesize,
5665                 parent_expr,
5666                 seq_no,
5667                 negate
5668     ) VALUES (
5669         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5670         'xnull',
5671         COALESCE(NEW.parenthesize, FALSE),
5672         NEW.parent_expr,
5673         COALESCE(NEW.seq_no, 1),
5674                 COALESCE(NEW.negate, false)
5675     );
5676
5677 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
5678     ON UPDATE TO query.expr_xnull
5679     DO INSTEAD
5680     UPDATE query.expression SET
5681         id = NEW.id,
5682         parenthesize = NEW.parenthesize,
5683         parent_expr = NEW.parent_expr,
5684         seq_no = NEW.seq_no,
5685                 negate = NEW.negate
5686     WHERE
5687         id = OLD.id;
5688
5689 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
5690     ON DELETE TO query.expr_xnull
5691     DO INSTEAD
5692     DELETE FROM query.expression WHERE id = OLD.id;
5693
5694 -- Create updatable view for numeric literal expressions
5695
5696 CREATE OR REPLACE VIEW query.expr_xnum AS
5697     SELECT
5698                 id,
5699                 parenthesize,
5700                 parent_expr,
5701                 seq_no,
5702                 literal
5703     FROM
5704         query.expression
5705     WHERE
5706         type = 'xnum';
5707
5708 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
5709     ON INSERT TO query.expr_xnum
5710     DO INSTEAD
5711     INSERT INTO query.expression (
5712                 id,
5713                 type,
5714                 parenthesize,
5715                 parent_expr,
5716                 seq_no,
5717                 literal
5718     ) VALUES (
5719         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5720         'xnum',
5721         COALESCE(NEW.parenthesize, FALSE),
5722         NEW.parent_expr,
5723         COALESCE(NEW.seq_no, 1),
5724         NEW.literal
5725     );
5726
5727 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
5728     ON UPDATE TO query.expr_xnum
5729     DO INSTEAD
5730     UPDATE query.expression SET
5731         id = NEW.id,
5732         parenthesize = NEW.parenthesize,
5733         parent_expr = NEW.parent_expr,
5734         seq_no = NEW.seq_no,
5735         literal = NEW.literal
5736     WHERE
5737         id = OLD.id;
5738
5739 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
5740     ON DELETE TO query.expr_xnum
5741     DO INSTEAD
5742     DELETE FROM query.expression WHERE id = OLD.id;
5743
5744 -- Create updatable view for operator expressions
5745
5746 CREATE OR REPLACE VIEW query.expr_xop AS
5747     SELECT
5748                 id,
5749                 parenthesize,
5750                 parent_expr,
5751                 seq_no,
5752                 left_operand,
5753                 operator,
5754                 right_operand,
5755                 negate
5756     FROM
5757         query.expression
5758     WHERE
5759         type = 'xop';
5760
5761 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
5762     ON INSERT TO query.expr_xop
5763     DO INSTEAD
5764     INSERT INTO query.expression (
5765                 id,
5766                 type,
5767                 parenthesize,
5768                 parent_expr,
5769                 seq_no,
5770                 left_operand,
5771                 operator,
5772                 right_operand,
5773                 negate
5774     ) VALUES (
5775         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5776         'xop',
5777         COALESCE(NEW.parenthesize, FALSE),
5778         NEW.parent_expr,
5779         COALESCE(NEW.seq_no, 1),
5780                 NEW.left_operand,
5781                 NEW.operator,
5782                 NEW.right_operand,
5783                 COALESCE(NEW.negate, false)
5784     );
5785
5786 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
5787     ON UPDATE TO query.expr_xop
5788     DO INSTEAD
5789     UPDATE query.expression SET
5790         id = NEW.id,
5791         parenthesize = NEW.parenthesize,
5792         parent_expr = NEW.parent_expr,
5793         seq_no = NEW.seq_no,
5794                 left_operand = NEW.left_operand,
5795                 operator = NEW.operator,
5796                 right_operand = NEW.right_operand,
5797                 negate = NEW.negate
5798     WHERE
5799         id = OLD.id;
5800
5801 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
5802     ON DELETE TO query.expr_xop
5803     DO INSTEAD
5804     DELETE FROM query.expression WHERE id = OLD.id;
5805
5806 -- Create updatable view for series expressions
5807 -- i.e. series of expressions separated by operators
5808
5809 CREATE OR REPLACE VIEW query.expr_xser AS
5810     SELECT
5811                 id,
5812                 parenthesize,
5813                 parent_expr,
5814                 seq_no,
5815                 operator,
5816                 negate
5817     FROM
5818         query.expression
5819     WHERE
5820         type = 'xser';
5821
5822 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
5823     ON INSERT TO query.expr_xser
5824     DO INSTEAD
5825     INSERT INTO query.expression (
5826                 id,
5827                 type,
5828                 parenthesize,
5829                 parent_expr,
5830                 seq_no,
5831                 operator,
5832                 negate
5833     ) VALUES (
5834         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5835         'xser',
5836         COALESCE(NEW.parenthesize, FALSE),
5837         NEW.parent_expr,
5838         COALESCE(NEW.seq_no, 1),
5839                 NEW.operator,
5840                 COALESCE(NEW.negate, false)
5841     );
5842
5843 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
5844     ON UPDATE TO query.expr_xser
5845     DO INSTEAD
5846     UPDATE query.expression SET
5847         id = NEW.id,
5848         parenthesize = NEW.parenthesize,
5849         parent_expr = NEW.parent_expr,
5850         seq_no = NEW.seq_no,
5851                 operator = NEW.operator,
5852                 negate = NEW.negate
5853     WHERE
5854         id = OLD.id;
5855
5856 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
5857     ON DELETE TO query.expr_xser
5858     DO INSTEAD
5859     DELETE FROM query.expression WHERE id = OLD.id;
5860
5861 -- Create updatable view for string literal expressions
5862
5863 CREATE OR REPLACE VIEW query.expr_xstr AS
5864     SELECT
5865         id,
5866         parenthesize,
5867         parent_expr,
5868         seq_no,
5869         literal
5870     FROM
5871         query.expression
5872     WHERE
5873         type = 'xstr';
5874
5875 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
5876     ON INSERT TO query.expr_xstr
5877     DO INSTEAD
5878     INSERT INTO query.expression (
5879         id,
5880         type,
5881         parenthesize,
5882         parent_expr,
5883         seq_no,
5884         literal
5885     ) VALUES (
5886         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5887         'xstr',
5888         COALESCE(NEW.parenthesize, FALSE),
5889         NEW.parent_expr,
5890         COALESCE(NEW.seq_no, 1),
5891         NEW.literal
5892     );
5893
5894 CREATE OR REPLACE RULE query_expr_string_update_rule AS
5895     ON UPDATE TO query.expr_xstr
5896     DO INSTEAD
5897     UPDATE query.expression SET
5898         id = NEW.id,
5899         parenthesize = NEW.parenthesize,
5900         parent_expr = NEW.parent_expr,
5901         seq_no = NEW.seq_no,
5902         literal = NEW.literal
5903     WHERE
5904         id = OLD.id;
5905
5906 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
5907     ON DELETE TO query.expr_xstr
5908     DO INSTEAD
5909     DELETE FROM query.expression WHERE id = OLD.id;
5910
5911 -- Create updatable view for subquery expressions
5912
5913 CREATE OR REPLACE VIEW query.expr_xsubq AS
5914     SELECT
5915                 id,
5916                 parenthesize,
5917                 parent_expr,
5918                 seq_no,
5919                 subquery,
5920                 negate
5921     FROM
5922         query.expression
5923     WHERE
5924         type = 'xsubq';
5925
5926 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
5927     ON INSERT TO query.expr_xsubq
5928     DO INSTEAD
5929     INSERT INTO query.expression (
5930                 id,
5931                 type,
5932                 parenthesize,
5933                 parent_expr,
5934                 seq_no,
5935                 subquery,
5936                 negate
5937     ) VALUES (
5938         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5939         'xsubq',
5940         COALESCE(NEW.parenthesize, FALSE),
5941         NEW.parent_expr,
5942         COALESCE(NEW.seq_no, 1),
5943                 NEW.subquery,
5944                 COALESCE(NEW.negate, false)
5945     );
5946
5947 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
5948     ON UPDATE TO query.expr_xsubq
5949     DO INSTEAD
5950     UPDATE query.expression SET
5951         id = NEW.id,
5952         parenthesize = NEW.parenthesize,
5953         parent_expr = NEW.parent_expr,
5954         seq_no = NEW.seq_no,
5955                 subquery = NEW.subquery,
5956                 negate = NEW.negate
5957     WHERE
5958         id = OLD.id;
5959
5960 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
5961     ON DELETE TO query.expr_xsubq
5962     DO INSTEAD
5963     DELETE FROM query.expression WHERE id = OLD.id;
5964
5965 CREATE TABLE action.fieldset (
5966     id              SERIAL          PRIMARY KEY,
5967     owner           INT             NOT NULL REFERENCES actor.usr (id)
5968                                     DEFERRABLE INITIALLY DEFERRED,
5969     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
5970                                     DEFERRABLE INITIALLY DEFERRED,
5971     status          TEXT            NOT NULL
5972                                     CONSTRAINT valid_status CHECK ( status in
5973                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
5974     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
5975     scheduled_time  TIMESTAMPTZ,
5976     applied_time    TIMESTAMPTZ,
5977     classname       TEXT            NOT NULL, -- an IDL class name
5978     name            TEXT            NOT NULL,
5979     stored_query    INT             REFERENCES query.stored_query (id)
5980                                     DEFERRABLE INITIALLY DEFERRED,
5981     pkey_value      TEXT,
5982     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
5983     CONSTRAINT fieldset_one_or_the_other CHECK (
5984         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
5985         (pkey_value IS NOT NULL AND stored_query IS NULL)
5986     )
5987     -- the CHECK constraint means we can update the fields for a single
5988     -- row without all the extra overhead involved in a query
5989 );
5990
5991 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
5992 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
5993
5994 CREATE TABLE action.fieldset_col_val (
5995     id              SERIAL  PRIMARY KEY,
5996     fieldset        INT     NOT NULL REFERENCES action.fieldset
5997                                          ON DELETE CASCADE
5998                                          DEFERRABLE INITIALLY DEFERRED,
5999     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
6000     val             TEXT,              -- value for the column ... NULL means, well, NULL
6001     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
6002 );
6003
6004 CREATE OR REPLACE FUNCTION action.apply_fieldset(
6005         fieldset_id IN INT,        -- id from action.fieldset
6006         table_name  IN TEXT,       -- table to be updated
6007         pkey_name   IN TEXT,       -- name of primary key column in that table
6008         query       IN TEXT        -- query constructed by qstore (for query-based
6009                                    --    fieldsets only; otherwise null
6010 )
6011 RETURNS TEXT AS $$
6012 DECLARE
6013         statement TEXT;
6014         fs_status TEXT;
6015         fs_pkey_value TEXT;
6016         fs_query TEXT;
6017         sep CHAR;
6018         status_code TEXT;
6019         msg TEXT;
6020         update_count INT;
6021         cv RECORD;
6022 BEGIN
6023         -- Sanity checks
6024         IF fieldset_id IS NULL THEN
6025                 RETURN 'Fieldset ID parameter is NULL';
6026         END IF;
6027         IF table_name IS NULL THEN
6028                 RETURN 'Table name parameter is NULL';
6029         END IF;
6030         IF pkey_name IS NULL THEN
6031                 RETURN 'Primary key name parameter is NULL';
6032         END IF;
6033         --
6034         statement := 'UPDATE ' || table_name || ' SET';
6035         --
6036         SELECT
6037                 status,
6038                 quote_literal( pkey_value )
6039         INTO
6040                 fs_status,
6041                 fs_pkey_value
6042         FROM
6043                 action.fieldset
6044         WHERE
6045                 id = fieldset_id;
6046         --
6047         IF fs_status IS NULL THEN
6048                 RETURN 'No fieldset found for id = ' || fieldset_id;
6049         ELSIF fs_status = 'APPLIED' THEN
6050                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
6051         END IF;
6052         --
6053         sep := '';
6054         FOR cv IN
6055                 SELECT  col,
6056                                 val
6057                 FROM    action.fieldset_col_val
6058                 WHERE   fieldset = fieldset_id
6059         LOOP
6060                 statement := statement || sep || ' ' || cv.col
6061                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
6062                 sep := ',';
6063         END LOOP;
6064         --
6065         IF sep = '' THEN
6066                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
6067         END IF;
6068         --
6069         -- Add the WHERE clause.  This differs according to whether it's a
6070         -- single-row fieldset or a query-based fieldset.
6071         --
6072         IF query IS NULL        AND fs_pkey_value IS NULL THEN
6073                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
6074         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
6075             fs_query := rtrim( query, ';' );
6076             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
6077                          || fs_query || ' );';
6078         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
6079                 statement := statement || ' WHERE ' || pkey_name || ' = '
6080                                      || fs_pkey_value || ';';
6081         ELSE  -- both are not null
6082                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
6083         END IF;
6084         --
6085         -- Execute the update
6086         --
6087         BEGIN
6088                 EXECUTE statement;
6089                 GET DIAGNOSTICS update_count = ROW_COUNT;
6090                 --
6091                 IF UPDATE_COUNT > 0 THEN
6092                         status_code := 'APPLIED';
6093                         msg := NULL;
6094                 ELSE
6095                         status_code := 'ERROR';
6096                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
6097         END IF;
6098         EXCEPTION WHEN OTHERS THEN
6099                 status_code := 'ERROR';
6100                 msg := 'Unable to apply fieldset ' || fieldset_id
6101                            || ': ' || sqlerrm;
6102         END;
6103         --
6104         -- Update fieldset status
6105         --
6106         UPDATE action.fieldset
6107         SET status       = status_code,
6108             applied_time = now()
6109         WHERE id = fieldset_id;
6110         --
6111         RETURN msg;
6112 END;
6113 $$ LANGUAGE plpgsql;
6114
6115 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
6116 /**
6117  * Applies a specified fieldset, using a supplied table name and primary
6118  * key name.  The query parameter should be non-null only for
6119  * query-based fieldsets.
6120  *
6121  * Returns NULL if successful, or an error message if not.
6122  */
6123 $$;
6124
6125 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
6126
6127 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
6128     SELECT  u.hold,
6129             c.circ_lib,
6130             count(*)
6131       FROM  action.unfulfilled_hold_list u
6132             JOIN asset.copy c ON (c.id = u.current_copy)
6133       GROUP BY 1,2;
6134
6135 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
6136     SELECT  hold,
6137             min(count)
6138       FROM  action.unfulfilled_hold_loops
6139       GROUP BY 1;
6140
6141 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
6142     SELECT  DISTINCT l.*
6143       FROM  action.unfulfilled_hold_loops l
6144             JOIN action.unfulfilled_hold_min_loop m USING (hold)
6145       WHERE l.count = m.min;
6146
6147 ALTER TABLE asset.copy
6148 ADD COLUMN dummy_isbn TEXT;
6149
6150 ALTER TABLE auditor.asset_copy_history
6151 ADD COLUMN dummy_isbn TEXT;
6152
6153 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
6154 -- Add corresponding new column to auditor.asset_copy_history
6155
6156 ALTER TABLE asset.copy
6157         ADD COLUMN status_changed_time TIMESTAMPTZ;
6158
6159 ALTER TABLE auditor.asset_copy_history
6160         ADD COLUMN status_changed_time TIMESTAMPTZ;
6161
6162 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
6163 RETURNS TRIGGER AS $$
6164 BEGIN
6165     IF NEW.status <> OLD.status THEN
6166         NEW.status_changed_time := now();
6167     END IF;
6168     RETURN NEW;
6169 END;
6170 $$ LANGUAGE plpgsql;
6171
6172 CREATE TRIGGER acp_status_changed_trig
6173         BEFORE UPDATE ON asset.copy
6174         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
6175
6176 ALTER TABLE asset.copy
6177 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6178
6179 ALTER TABLE auditor.asset_copy_history
6180 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6181
6182 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
6183 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
6184
6185 DROP INDEX IF EXISTS asset.copy_barcode_key;
6186 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
6187
6188 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
6189 -- AFTER INSERT OR UPDATE ON asset.copy
6190
6191 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
6192 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
6193
6194 -- Moke mostly parallel changes to action.circulation
6195 -- and action.aged_circulation
6196
6197 ALTER TABLE action.circulation
6198 ADD COLUMN workstation INT
6199     REFERENCES actor.workstation
6200         ON DELETE SET NULL
6201         DEFERRABLE INITIALLY DEFERRED;
6202
6203 ALTER TABLE action.aged_circulation
6204 ADD COLUMN workstation INT;
6205
6206 ALTER TABLE action.circulation
6207 ADD COLUMN parent_circ BIGINT
6208         REFERENCES action.circulation(id)
6209         DEFERRABLE INITIALLY DEFERRED;
6210
6211 CREATE UNIQUE INDEX circ_parent_idx
6212 ON action.circulation( parent_circ )
6213 WHERE parent_circ IS NOT NULL;
6214
6215 ALTER TABLE action.aged_circulation
6216 ADD COLUMN parent_circ BIGINT;
6217
6218 ALTER TABLE action.circulation
6219 ADD COLUMN checkin_workstation INT
6220         REFERENCES actor.workstation(id)
6221         ON DELETE SET NULL
6222         DEFERRABLE INITIALLY DEFERRED;
6223
6224 ALTER TABLE action.aged_circulation
6225 ADD COLUMN checkin_workstation INT;
6226
6227 ALTER TABLE action.circulation
6228 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6229
6230 ALTER TABLE action.aged_circulation
6231 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6232
6233 CREATE INDEX action_circulation_target_copy_idx
6234 ON action.circulation (target_copy);
6235
6236 CREATE INDEX action_aged_circulation_target_copy_idx
6237 ON action.aged_circulation (target_copy);
6238
6239 ALTER TABLE action.circulation
6240 DROP CONSTRAINT circulation_stop_fines_check;
6241
6242 ALTER TABLE action.circulation
6243         ADD CONSTRAINT circulation_stop_fines_check
6244         CHECK (stop_fines IN (
6245         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
6246
6247 -- Hard due-date functionality
6248 CREATE TABLE config.hard_due_date (
6249         id          SERIAL      PRIMARY KEY,
6250         name        TEXT        NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
6251         ceiling_date    TIMESTAMPTZ NOT NULL,
6252         forceto     BOOL        NOT NULL,
6253         owner       INT         NOT NULL
6254 );
6255
6256 CREATE TABLE config.hard_due_date_values (
6257     id                  SERIAL      PRIMARY KEY,
6258     hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
6259                                     DEFERRABLE INITIALLY DEFERRED,
6260     ceiling_date        TIMESTAMPTZ NOT NULL,
6261     active_date         TIMESTAMPTZ NOT NULL
6262 );
6263
6264 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN hard_due_date INT REFERENCES config.hard_due_date (id);
6265
6266 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
6267 DECLARE
6268     temp_value  config.hard_due_date_values%ROWTYPE;
6269     updated     INT := 0;
6270 BEGIN
6271     FOR temp_value IN
6272       SELECT  DISTINCT ON (hard_due_date) *
6273         FROM  config.hard_due_date_values
6274         WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
6275         ORDER BY active_date DESC -- Latest (nearest to us) active time
6276    LOOP
6277         UPDATE  config.hard_due_date
6278           SET   ceiling_date = temp_value.ceiling_date
6279           WHERE id = temp_value.hard_due_date
6280                 AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd
6281
6282         IF FOUND THEN
6283             updated := updated + 1;
6284         END IF;
6285     END LOOP;
6286
6287     RETURN updated;
6288 END;
6289 $func$ LANGUAGE plpgsql;
6290
6291 -- Correct some long-standing misspellings involving variations of "recur"
6292
6293 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
6294 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6295
6296 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
6297 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6298
6299 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
6300 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
6301
6302 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
6303
6304 -- Might as well keep the comment in sync as well
6305 COMMENT ON TABLE config.rule_recurring_fine IS $$
6306 /*
6307  * Copyright (C) 2005  Georgia Public Library Service 
6308  * Mike Rylander <mrylander@gmail.com>
6309  *
6310  * Circulation Recurring Fine rules
6311  *
6312  * Each circulation is given a recurring fine amount based on one of
6313  * these rules.  The recurrence_interval should not be any shorter
6314  * than the interval between runs of the fine_processor.pl script
6315  * (which is run from CRON), or you could miss fines.
6316  * 
6317  *
6318  * ****
6319  *
6320  * This program is free software; you can redistribute it and/or
6321  * modify it under the terms of the GNU General Public License
6322  * as published by the Free Software Foundation; either version 2
6323  * of the License, or (at your option) any later version.
6324  *
6325  * This program is distributed in the hope that it will be useful,
6326  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6327  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6328  * GNU General Public License for more details.
6329  */
6330 $$;
6331
6332 -- Extend the name change to some related views:
6333
6334 DROP VIEW IF EXISTS reporter.overdue_circs;
6335
6336 CREATE OR REPLACE VIEW reporter.overdue_circs AS
6337 SELECT  *
6338   FROM  action.circulation
6339     WHERE checkin_time is null
6340                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
6341                                 AND due_date < now();
6342
6343 DROP VIEW IF EXISTS stats.fleshed_circulation;
6344
6345 DROP VIEW IF EXISTS stats.fleshed_copy;
6346
6347 CREATE VIEW stats.fleshed_copy AS
6348         SELECT  cp.*,
6349         CAST(cp.create_date AS DATE) AS create_date_day,
6350         CAST(cp.edit_date AS DATE) AS edit_date_day,
6351         DATE_TRUNC('hour', cp.create_date) AS create_date_hour,
6352         DATE_TRUNC('hour', cp.edit_date) AS edit_date_hour,
6353                 cn.label AS call_number_label,
6354                 cn.owning_lib,
6355                 rd.item_lang,
6356                 rd.item_type,
6357                 rd.item_form
6358         FROM    asset.copy cp
6359                 JOIN asset.call_number cn ON (cp.call_number = cn.id)
6360                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
6361
6362 CREATE VIEW stats.fleshed_circulation AS
6363         SELECT  c.*,
6364                 CAST(c.xact_start AS DATE) AS start_date_day,
6365                 CAST(c.xact_finish AS DATE) AS finish_date_day,
6366                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
6367                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
6368                 cp.call_number_label,
6369                 cp.owning_lib,
6370                 cp.item_lang,
6371                 cp.item_type,
6372                 cp.item_form
6373         FROM    action.circulation c
6374                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
6375
6376 -- Drop a view temporarily in order to alter action.all_circulation, upon
6377 -- which it is dependent.  We will recreate the view later.
6378
6379 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
6380
6381 -- You would think that CREATE OR REPLACE would be enough, but in testing
6382 -- PostgreSQL complained about renaming the columns in the view. So we
6383 -- drop the view first.
6384 DROP VIEW IF EXISTS action.all_circulation;
6385
6386 CREATE OR REPLACE VIEW action.all_circulation AS
6387     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6388         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6389         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6390         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6391         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6392         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6393       FROM  action.aged_circulation
6394             UNION ALL
6395     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,
6396         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,
6397         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6398         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6399         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6400         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6401         circ.parent_circ
6402       FROM  action.circulation circ
6403         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6404         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6405         JOIN actor.usr p ON (circ.usr = p.id)
6406         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6407         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
6408
6409 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
6410
6411 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
6412  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
6413    FROM asset."copy" cp
6414    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
6415    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
6416    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
6417   GROUP BY cp.id;
6418
6419 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
6420
6421 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
6422
6423 -- Rebuild dependent views
6424
6425 DROP VIEW IF EXISTS action.billable_circulations;
6426
6427 CREATE OR REPLACE VIEW action.billable_circulations AS
6428     SELECT  *
6429       FROM  action.circulation
6430       WHERE xact_finish IS NULL;
6431
6432 DROP VIEW IF EXISTS action.open_circulation;
6433
6434 CREATE OR REPLACE VIEW action.open_circulation AS
6435     SELECT  *
6436       FROM  action.circulation
6437       WHERE checkin_time IS NULL
6438       ORDER BY due_date;
6439
6440 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
6441 DECLARE
6442 found char := 'N';
6443 BEGIN
6444
6445     -- If there are any renewals for this circulation, don't archive or delete
6446     -- it yet.   We'll do so later, when we archive and delete the renewals.
6447
6448     SELECT 'Y' INTO found
6449     FROM action.circulation
6450     WHERE parent_circ = OLD.id
6451     LIMIT 1;
6452
6453     IF found = 'Y' THEN
6454         RETURN NULL;  -- don't delete
6455         END IF;
6456
6457     -- Archive a copy of the old row to action.aged_circulation
6458
6459     INSERT INTO action.aged_circulation
6460         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6461         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6462         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6463         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6464         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6465         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
6466       SELECT
6467         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6468         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6469         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6470         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6471         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6472         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6473         FROM action.all_circulation WHERE id = OLD.id;
6474
6475     RETURN OLD;
6476 END;
6477 $$ LANGUAGE 'plpgsql';
6478
6479 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
6480
6481 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
6482
6483 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
6484
6485 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$
6486 DECLARE
6487     current_requestor_group    permission.grp_tree%ROWTYPE;
6488     requestor_object    actor.usr%ROWTYPE;
6489     user_object        actor.usr%ROWTYPE;
6490     item_object        asset.copy%ROWTYPE;
6491     item_cn_object        asset.call_number%ROWTYPE;
6492     rec_descriptor        metabib.rec_descriptor%ROWTYPE;
6493     current_mp_weight    FLOAT;
6494     matchpoint_weight    FLOAT;
6495     tmp_weight        FLOAT;
6496     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
6497     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
6498 BEGIN
6499     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6500     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
6501     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6502     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6503     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
6504
6505     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
6506
6507     IF NOT FOUND THEN
6508         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
6509     ELSE
6510         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
6511     END IF;
6512
6513     LOOP 
6514         -- for each potential matchpoint for this ou and group ...
6515         FOR current_mp IN
6516             SELECT    m.*
6517               FROM    config.hold_matrix_matchpoint m
6518               WHERE    m.requestor_grp = current_requestor_group.id AND m.active
6519               ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
6520                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
6521                     CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
6522                     CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
6523                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
6524                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
6525
6526             current_mp_weight := 5.0;
6527
6528             IF current_mp.circ_modifier IS NOT NULL THEN
6529                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6530             END IF;
6531
6532             IF current_mp.marc_type IS NOT NULL THEN
6533                 IF item_object.circ_as_type IS NOT NULL THEN
6534                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6535                 ELSE
6536                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6537                 END IF;
6538             END IF;
6539
6540             IF current_mp.marc_form IS NOT NULL THEN
6541                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6542             END IF;
6543
6544             IF current_mp.marc_vr_format IS NOT NULL THEN
6545                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6546             END IF;
6547
6548             IF current_mp.juvenile_flag IS NOT NULL THEN
6549                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6550             END IF;
6551
6552             IF current_mp.ref_flag IS NOT NULL THEN
6553                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6554             END IF;
6555
6556
6557             -- caclulate the rule match weight
6558             IF current_mp.item_owning_ou IS NOT NULL THEN
6559                 CONTINUE WHEN current_mp.item_owning_ou NOT IN (SELECT (actor.org_unit_ancestors(item_cn_object.owning_lib)).id);
6560                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
6561                 current_mp_weight := current_mp_weight - tmp_weight;
6562             END IF; 
6563
6564             IF current_mp.item_circ_ou IS NOT NULL THEN
6565                 CONTINUE WHEN current_mp.item_circ_ou NOT IN (SELECT (actor.org_unit_ancestors(item_object.circ_lib)).id);
6566                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
6567                 current_mp_weight := current_mp_weight - tmp_weight;
6568             END IF; 
6569
6570             IF current_mp.pickup_ou IS NOT NULL THEN
6571                 CONTINUE WHEN current_mp.pickup_ou NOT IN (SELECT (actor.org_unit_ancestors(pickup_ou)).id);
6572                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
6573                 current_mp_weight := current_mp_weight - tmp_weight;
6574             END IF; 
6575
6576             IF current_mp.request_ou IS NOT NULL THEN
6577                 CONTINUE WHEN current_mp.request_ou NOT IN (SELECT (actor.org_unit_ancestors(request_ou)).id);
6578                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
6579                 current_mp_weight := current_mp_weight - tmp_weight;
6580             END IF; 
6581
6582             IF current_mp.user_home_ou IS NOT NULL THEN
6583                 CONTINUE WHEN current_mp.user_home_ou NOT IN (SELECT (actor.org_unit_ancestors(user_object.home_ou)).id);
6584                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
6585                 current_mp_weight := current_mp_weight - tmp_weight;
6586             END IF; 
6587
6588             -- set the matchpoint if we found the best one
6589             IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
6590                 matchpoint = current_mp;
6591                 matchpoint_weight = current_mp_weight;
6592             END IF;
6593
6594         END LOOP;
6595
6596         EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6597
6598         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
6599     END LOOP;
6600
6601     RETURN matchpoint.id;
6602 END;
6603 $func$ LANGUAGE plpgsql;
6604
6605 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$
6606 DECLARE
6607     matchpoint_id        INT;
6608     user_object        actor.usr%ROWTYPE;
6609     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
6610     standing_penalty    config.standing_penalty%ROWTYPE;
6611     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
6612     transit_source        actor.org_unit%ROWTYPE;
6613     item_object        asset.copy%ROWTYPE;
6614     ou_skip              actor.org_unit_setting%ROWTYPE;
6615     result            action.matrix_test_result;
6616     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
6617     hold_count        INT;
6618     hold_transit_prox    INT;
6619     frozen_hold_count    INT;
6620     context_org_list    INT[];
6621     done            BOOL := FALSE;
6622 BEGIN
6623     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6624     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
6625
6626     result.success := TRUE;
6627
6628     -- Fail if we couldn't find a user
6629     IF user_object.id IS NULL THEN
6630         result.fail_part := 'no_user';
6631         result.success := FALSE;
6632         done := TRUE;
6633         RETURN NEXT result;
6634         RETURN;
6635     END IF;
6636
6637     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6638
6639     -- Fail if we couldn't find a copy
6640     IF item_object.id IS NULL THEN
6641         result.fail_part := 'no_item';
6642         result.success := FALSE;
6643         done := TRUE;
6644         RETURN NEXT result;
6645         RETURN;
6646     END IF;
6647
6648     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
6649     result.matchpoint := matchpoint_id;
6650
6651     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
6652
6653     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
6654     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
6655         result.fail_part := 'circ.holds.target_skip_me';
6656         result.success := FALSE;
6657         done := TRUE;
6658         RETURN NEXT result;
6659         RETURN;
6660     END IF;
6661
6662     -- Fail if user is barred
6663     IF user_object.barred IS TRUE THEN
6664         result.fail_part := 'actor.usr.barred';
6665         result.success := FALSE;
6666         done := TRUE;
6667         RETURN NEXT result;
6668         RETURN;
6669     END IF;
6670
6671     -- Fail if we couldn't find any matchpoint (requires a default)
6672     IF matchpoint_id IS NULL THEN
6673         result.fail_part := 'no_matchpoint';
6674         result.success := FALSE;
6675         done := TRUE;
6676         RETURN NEXT result;
6677         RETURN;
6678     END IF;
6679
6680     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
6681
6682     IF hold_test.holdable IS FALSE THEN
6683         result.fail_part := 'config.hold_matrix_test.holdable';
6684         result.success := FALSE;
6685         done := TRUE;
6686         RETURN NEXT result;
6687     END IF;
6688
6689     IF hold_test.transit_range IS NOT NULL THEN
6690         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
6691         IF hold_test.distance_is_from_owner THEN
6692             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;
6693         ELSE
6694             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
6695         END IF;
6696
6697         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
6698
6699         IF NOT FOUND THEN
6700             result.fail_part := 'transit_range';
6701             result.success := FALSE;
6702             done := TRUE;
6703             RETURN NEXT result;
6704         END IF;
6705     END IF;
6706  
6707     IF NOT retargetting THEN
6708         FOR standing_penalty IN
6709             SELECT  DISTINCT csp.*
6710               FROM  actor.usr_standing_penalty usp
6711                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6712               WHERE usr = match_user
6713                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6714                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6715                     AND csp.block_list LIKE '%HOLD%' LOOP
6716     
6717             result.fail_part := standing_penalty.name;
6718             result.success := FALSE;
6719             done := TRUE;
6720             RETURN NEXT result;
6721         END LOOP;
6722     
6723         IF hold_test.stop_blocked_user IS TRUE THEN
6724             FOR standing_penalty IN
6725                 SELECT  DISTINCT csp.*
6726                   FROM  actor.usr_standing_penalty usp
6727                         JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6728                   WHERE usr = match_user
6729                         AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6730                         AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6731                         AND csp.block_list LIKE '%CIRC%' LOOP
6732         
6733                 result.fail_part := standing_penalty.name;
6734                 result.success := FALSE;
6735                 done := TRUE;
6736                 RETURN NEXT result;
6737             END LOOP;
6738         END IF;
6739     
6740         IF hold_test.max_holds IS NOT NULL THEN
6741             SELECT    INTO hold_count COUNT(*)
6742               FROM    action.hold_request
6743               WHERE    usr = match_user
6744                 AND fulfillment_time IS NULL
6745                 AND cancel_time IS NULL
6746                 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
6747     
6748             IF hold_count >= hold_test.max_holds THEN
6749                 result.fail_part := 'config.hold_matrix_test.max_holds';
6750                 result.success := FALSE;
6751                 done := TRUE;
6752                 RETURN NEXT result;
6753             END IF;
6754         END IF;
6755     END IF;
6756
6757     IF item_object.age_protect IS NOT NULL THEN
6758         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
6759
6760         IF item_object.create_date + age_protect_object.age > NOW() THEN
6761             IF hold_test.distance_is_from_owner THEN
6762                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
6763             ELSE
6764                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
6765             END IF;
6766
6767             IF hold_transit_prox > age_protect_object.prox THEN
6768                 result.fail_part := 'config.rule_age_hold_protect.prox';
6769                 result.success := FALSE;
6770                 done := TRUE;
6771                 RETURN NEXT result;
6772             END IF;
6773         END IF;
6774     END IF;
6775
6776     IF NOT done THEN
6777         RETURN NEXT result;
6778     END IF;
6779
6780     RETURN;
6781 END;
6782 $func$ LANGUAGE plpgsql;
6783
6784 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$
6785     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE);
6786 $func$ LANGUAGE SQL;
6787
6788 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$
6789     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
6790 $func$ LANGUAGE SQL;
6791
6792 -- New post-delete trigger to propagate deletions to parent(s)
6793
6794 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
6795 BEGIN
6796
6797     -- Having deleted a renewal, we can delete the original circulation (or a previous
6798     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
6799     -- deletion of any prior parents, etc. recursively.
6800
6801     IF OLD.parent_circ IS NOT NULL THEN
6802         DELETE FROM action.circulation
6803         WHERE id = OLD.parent_circ;
6804     END IF;
6805
6806     RETURN OLD;
6807 END;
6808 $$ LANGUAGE 'plpgsql';
6809
6810 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
6811 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
6812
6813 -- This only gets inserted if there are no other id > 100 billing types
6814 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;
6815 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
6816
6817 -- Populate xact_type column in the materialized version of billable_xact_summary
6818
6819 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
6820 BEGIN
6821         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
6822                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
6823         RETURN NEW;
6824 END;
6825 $$ LANGUAGE PLPGSQL;
6826  
6827 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
6828 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
6829  
6830 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
6831 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
6832
6833 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
6834     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;
6835
6836 -- Generate the equivalent of compound subject entries from the existing rows
6837 -- so that we don't have to laboriously reindex them
6838
6839 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
6840 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
6841 --
6842 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
6843 --
6844 --INSERT INTO metabib.subject_field_entry (source, field, value)
6845 --    SELECT source, (
6846 --            SELECT id 
6847 --            FROM config.metabib_field
6848 --            WHERE field_class = 'subject' AND name = 'complete'
6849 --        ), 
6850 --        ARRAY_TO_STRING ( 
6851 --            ARRAY (
6852 --                SELECT value 
6853 --                FROM metabib.subject_field_entry msfe
6854 --                WHERE msfe.source = groupee.source
6855 --                ORDER BY source 
6856 --            ), ' ' 
6857 --        ) AS grouped
6858 --    FROM ( 
6859 --        SELECT source
6860 --        FROM metabib.subject_field_entry
6861 --        GROUP BY source
6862 --    ) AS groupee;
6863
6864 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
6865 DECLARE
6866         prev_billing    money.billing%ROWTYPE;
6867         old_billing     money.billing%ROWTYPE;
6868 BEGIN
6869         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
6870         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
6871
6872         IF OLD.id = old_billing.id THEN
6873                 UPDATE  money.materialized_billable_xact_summary
6874                   SET   last_billing_ts = prev_billing.billing_ts,
6875                         last_billing_note = prev_billing.note,
6876                         last_billing_type = prev_billing.billing_type
6877                   WHERE id = OLD.xact;
6878         END IF;
6879
6880         IF NOT OLD.voided THEN
6881                 UPDATE  money.materialized_billable_xact_summary
6882                   SET   total_owed = total_owed - OLD.amount,
6883                         balance_owed = balance_owed + OLD.amount
6884                   WHERE id = OLD.xact;
6885         END IF;
6886
6887         RETURN OLD;
6888 END;
6889 $$ LANGUAGE PLPGSQL;
6890
6891 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
6892 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
6893
6894 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
6895     use Unicode::Normalize;
6896     use Encode;
6897
6898     # When working with Unicode data, the first step is to decode it to
6899     # a byte string; after that, lowercasing is safe
6900     my $txt = lc(decode_utf8(shift));
6901     my $sf = shift;
6902
6903     $txt = NFD($txt);
6904     $txt =~ s/\pM+//go; # Remove diacritics
6905
6906     # remove non-combining diacritics
6907     # this list of characters follows the NACO normalization spec,
6908     # but a looser but more comprehensive version might be
6909     # $txt =~ s/\pLm+//go;
6910     $txt =~ tr/\x{02B9}\x{02BA}\x{02BB}\x{02BC}//d;
6911
6912     $txt =~ s/\xE6/AE/go;   # Convert ae digraph
6913     $txt =~ s/\x{153}/OE/go;# Convert oe digraph
6914     $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
6915
6916     $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
6917     $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
6918
6919     $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;     # Convert Latin and Greek
6920     $txt =~ tr/\x{2113}\xF0\x{111}\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LDD /; # Convert Misc
6921     $txt =~ tr/\'\[\]\|//d;                         # Remove Misc
6922
6923     if ($sf && $sf =~ /^a/o) {
6924         my $commapos = index($txt,',');
6925         if ($commapos > -1) {
6926             if ($commapos != length($txt) - 1) {
6927                 my @list = split /,/, $txt;
6928                 my $first = shift @list;
6929                 $txt = $first . ',' . join(' ', @list);
6930             } else {
6931                 $txt =~ s/,/ /go;
6932             }
6933         }
6934     } else {
6935         $txt =~ s/,/ /go;
6936     }
6937
6938     $txt =~ s/\s+/ /go; # Compress multiple spaces
6939     $txt =~ s/^\s+//o;  # Remove leading space
6940     $txt =~ s/\s+$//o;  # Remove trailing space
6941
6942     # Encoding the outgoing string is good practice, but not strictly
6943     # necessary in this case because we've stripped everything from it
6944     return encode_utf8($txt);
6945 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
6946
6947 -- Some handy functions, based on existing ones, to provide optional ingest normalization
6948
6949 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6950         SELECT SUBSTRING($1,$2);
6951 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6952
6953 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6954         SELECT SUBSTRING($1,1,$2);
6955 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6956
6957 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
6958         SELECT public.naco_normalize($1,'a');
6959 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6960
6961 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
6962         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
6963 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6964
6965 -- And ... a table in which to register them
6966
6967 CREATE TABLE config.index_normalizer (
6968         id              SERIAL  PRIMARY KEY,
6969         name            TEXT    UNIQUE NOT NULL,
6970         description     TEXT,
6971         func            TEXT    NOT NULL,
6972         param_count     INT     NOT NULL DEFAULT 0
6973 );
6974
6975 CREATE TABLE config.metabib_field_index_norm_map (
6976         id      SERIAL  PRIMARY KEY,
6977         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6978         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6979         params  TEXT,
6980         pos     INT     NOT NULL DEFAULT 0
6981 );
6982
6983 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6984         'NACO Normalize',
6985         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
6986         'naco_normalize',
6987         0
6988 );
6989
6990 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6991         'Normalize date range',
6992         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
6993         'split_date_range',
6994         1
6995 );
6996
6997 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6998         'NACO Normalize -- retain first comma',
6999         '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.',
7000         'naco_normalize_keep_comma',
7001         0
7002 );
7003
7004 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7005         'Strip Diacritics',
7006         'Convert text to NFD form and remove non-spacing combining marks.',
7007         'remove_diacritics',
7008         0
7009 );
7010
7011 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7012         'Up-case',
7013         'Convert text upper case.',
7014         'uppercase',
7015         0
7016 );
7017
7018 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7019         'Down-case',
7020         'Convert text lower case.',
7021         'lowercase',
7022         0
7023 );
7024
7025 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7026         'Extract Dewey-like number',
7027         'Extract a string of numeric characters ther resembles a DDC number.',
7028         'call_number_dewey',
7029         0
7030 );
7031
7032 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7033         'Left truncation',
7034         'Discard the specified number of characters from the left side of the string.',
7035         'left_trunc',
7036         1
7037 );
7038
7039 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7040         'Right truncation',
7041         'Include only the specified number of characters from the left side of the string.',
7042         'right_trunc',
7043         1
7044 );
7045
7046 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7047         'First word',
7048         'Include only the first space-separated word of a string.',
7049         'first_word',
7050         0
7051 );
7052
7053 INSERT INTO config.metabib_field_index_norm_map (field,norm)
7054         SELECT  m.id,
7055                 i.id
7056           FROM  config.metabib_field m,
7057                 config.index_normalizer i
7058           WHERE i.func IN ('naco_normalize','split_date_range');
7059
7060 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
7061 DECLARE
7062     normalizer      RECORD;
7063     value           TEXT := '';
7064 BEGIN
7065
7066     value := NEW.value;
7067
7068     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7069         FOR normalizer IN
7070             SELECT  n.func AS func,
7071                     n.param_count AS param_count,
7072                     m.params AS params
7073               FROM  config.index_normalizer n
7074                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7075               WHERE field = NEW.field AND m.pos < 0
7076               ORDER BY m.pos LOOP
7077                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7078                     quote_literal( value ) ||
7079                     CASE
7080                         WHEN normalizer.param_count > 0
7081                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7082                             ELSE ''
7083                         END ||
7084                     ')' INTO value;
7085
7086         END LOOP;
7087
7088         NEW.value := value;
7089     END IF;
7090
7091     IF NEW.index_vector = ''::tsvector THEN
7092         RETURN NEW;
7093     END IF;
7094
7095     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7096         FOR normalizer IN
7097             SELECT  n.func AS func,
7098                     n.param_count AS param_count,
7099                     m.params AS params
7100               FROM  config.index_normalizer n
7101                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7102               WHERE field = NEW.field AND m.pos >= 0
7103               ORDER BY m.pos LOOP
7104                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7105                     quote_literal( value ) ||
7106                     CASE
7107                         WHEN normalizer.param_count > 0
7108                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7109                             ELSE ''
7110                         END ||
7111                     ')' INTO value;
7112
7113         END LOOP;
7114     END IF;
7115
7116     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
7117         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
7118     ELSE
7119         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
7120     END IF;
7121
7122     RETURN NEW;
7123 END;
7124 $$ LANGUAGE PLPGSQL;
7125
7126 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7127
7128 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7129
7130 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7131     SELECT  ARRAY_TO_STRING(
7132                 oils_xpath(
7133                     $1 ||
7134                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
7135                     $2,
7136                     $4
7137                 ),
7138                 $3
7139             );
7140 $func$ LANGUAGE SQL IMMUTABLE;
7141
7142 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
7143     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
7144 $func$ LANGUAGE SQL IMMUTABLE;
7145
7146 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7147     SELECT oils_xpath_string( $1, $2, '', $3 );
7148 $func$ LANGUAGE SQL IMMUTABLE;
7149
7150 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
7151     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
7152 $func$ LANGUAGE SQL IMMUTABLE;
7153
7154 CREATE TYPE metabib.field_entry_template AS (
7155         field_class     TEXT,
7156         field           INT,
7157         source          BIGINT,
7158         value           TEXT
7159 );
7160
7161 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7162   use strict;
7163
7164   use XML::LibXSLT;
7165   use XML::LibXML;
7166
7167   my $doc = shift;
7168   my $xslt = shift;
7169
7170   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7171   # methods of parsing XML documents and stylesheets, in the hopes of broader
7172   # compatibility with distributions
7173   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7174
7175   # Cache the XML parser, if we do not already have one
7176   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7177     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7178
7179   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7180
7181   # Cache the XSLT processor, if we do not already have one
7182   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7183     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7184
7185   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7186     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7187
7188   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7189     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7190
7191   return $stylesheet->output_string(
7192     $stylesheet->transform(
7193       $parser->parse_string($doc)
7194     )
7195   );
7196
7197 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7198
7199 -- Add two columns so that the following function will compile.
7200 -- Eventually the label column will be NOT NULL, but not yet.
7201 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7202 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7203
7204 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7205 DECLARE
7206     bib     biblio.record_entry%ROWTYPE;
7207     idx     config.metabib_field%ROWTYPE;
7208     xfrm        config.xml_transform%ROWTYPE;
7209     prev_xfrm   TEXT;
7210     transformed_xml TEXT;
7211     xml_node    TEXT;
7212     xml_node_list   TEXT[];
7213     facet_text  TEXT;
7214     raw_text    TEXT;
7215     curr_text   TEXT;
7216     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7217     output_row  metabib.field_entry_template%ROWTYPE;
7218 BEGIN
7219
7220     -- Get the record
7221     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7222
7223     -- Loop over the indexing entries
7224     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7225
7226         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7227
7228         -- See if we can skip the XSLT ... it's expensive
7229         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7230             -- Can't skip the transform
7231             IF xfrm.xslt <> '---' THEN
7232                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7233             ELSE
7234                 transformed_xml := bib.marc;
7235             END IF;
7236
7237             prev_xfrm := xfrm.name;
7238         END IF;
7239
7240         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7241
7242         raw_text := NULL;
7243         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7244             CONTINUE WHEN xml_node !~ E'^\\s*<';
7245
7246             curr_text := ARRAY_TO_STRING(
7247                 oils_xpath( '//text()',
7248                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7249                         REGEXP_REPLACE( -- This escapes embeded <s
7250                             xml_node,
7251                             $re$(>[^<]+)(<)([^>]+<)$re$,
7252                             E'\\1&lt;\\3',
7253                             'g'
7254                         ),
7255                         '&(?!amp;)',
7256                         '&amp;',
7257                         'g'
7258                     )
7259                 ),
7260                 ' '
7261             );
7262
7263             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7264
7265             IF raw_text IS NOT NULL THEN
7266                 raw_text := raw_text || joiner;
7267             END IF;
7268
7269             raw_text := COALESCE(raw_text,'') || curr_text;
7270
7271             -- insert raw node text for faceting
7272             IF idx.facet_field THEN
7273
7274                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7275                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7276                 ELSE
7277                     facet_text := curr_text;
7278                 END IF;
7279
7280                 output_row.field_class = idx.field_class;
7281                 output_row.field = -1 * idx.id;
7282                 output_row.source = rid;
7283                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7284
7285                 RETURN NEXT output_row;
7286             END IF;
7287
7288         END LOOP;
7289
7290         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7291
7292         -- insert combined node text for searching
7293         IF idx.search_field THEN
7294             output_row.field_class = idx.field_class;
7295             output_row.field = idx.id;
7296             output_row.source = rid;
7297             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7298
7299             RETURN NEXT output_row;
7300         END IF;
7301
7302     END LOOP;
7303
7304 END;
7305 $func$ LANGUAGE PLPGSQL;
7306
7307 -- default to a space joiner
7308 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7309         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7310 $func$ LANGUAGE SQL;
7311
7312 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7313
7314 use MARC::Record;
7315 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7316
7317 my $xml = shift;
7318 my $r = MARC::Record->new_from_xml( $xml );
7319
7320 return_next( { tag => 'LDR', value => $r->leader } );
7321
7322 for my $f ( $r->fields ) {
7323     if ($f->is_control_field) {
7324         return_next({ tag => $f->tag, value => $f->data });
7325     } else {
7326         for my $s ($f->subfields) {
7327             return_next({
7328                 tag      => $f->tag,
7329                 ind1     => $f->indicator(1),
7330                 ind2     => $f->indicator(2),
7331                 subfield => $s->[0],
7332                 value    => $s->[1]
7333             });
7334
7335             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7336                 my $trim = $f->indicator(2) || 0;
7337                 return_next({
7338                     tag      => 'tnf',
7339                     ind1     => $f->indicator(1),
7340                     ind2     => $f->indicator(2),
7341                     subfield => 'a',
7342                     value    => substr( $s->[1], $trim )
7343                 });
7344             }
7345         }
7346     }
7347 }
7348
7349 return undef;
7350
7351 $func$ LANGUAGE PLPERLU;
7352
7353 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7354 DECLARE
7355     bib biblio.record_entry%ROWTYPE;
7356     output  metabib.full_rec%ROWTYPE;
7357     field   RECORD;
7358 BEGIN
7359     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7360
7361     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7362         output.record := rid;
7363         output.ind1 := field.ind1;
7364         output.ind2 := field.ind2;
7365         output.tag := field.tag;
7366         output.subfield := field.subfield;
7367         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7368             output.value := naco_normalize(field.value, field.subfield);
7369         ELSE
7370             output.value := field.value;
7371         END IF;
7372
7373         CONTINUE WHEN output.value IS NULL;
7374
7375         RETURN NEXT output;
7376     END LOOP;
7377 END;
7378 $func$ LANGUAGE PLPGSQL;
7379
7380 -- functions to create auditor objects
7381
7382 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7383 BEGIN
7384     EXECUTE $$
7385         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7386     $$;
7387         RETURN TRUE;
7388 END;
7389 $creator$ LANGUAGE 'plpgsql';
7390
7391 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7392 BEGIN
7393     EXECUTE $$
7394         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7395             audit_id    BIGINT                          PRIMARY KEY,
7396             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7397             audit_action        TEXT                            NOT NULL,
7398             LIKE $$ || sch || $$.$$ || tbl || $$
7399         );
7400     $$;
7401         RETURN TRUE;
7402 END;
7403 $creator$ LANGUAGE 'plpgsql';
7404
7405 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7406 BEGIN
7407     EXECUTE $$
7408         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7409         RETURNS TRIGGER AS $func$
7410         BEGIN
7411             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7412                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7413                     now(),
7414                     SUBSTR(TG_OP,1,1),
7415                     OLD.*;
7416             RETURN NULL;
7417         END;
7418         $func$ LANGUAGE 'plpgsql';
7419     $$;
7420         RETURN TRUE;
7421 END;
7422 $creator$ LANGUAGE 'plpgsql';
7423
7424 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7425 BEGIN
7426     EXECUTE $$
7427         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7428             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7429             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7430     $$;
7431         RETURN TRUE;
7432 END;
7433 $creator$ LANGUAGE 'plpgsql';
7434
7435 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7436 BEGIN
7437     EXECUTE $$
7438         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7439             SELECT      -1, now() as audit_time, '-' as audit_action, *
7440               FROM      $$ || sch || $$.$$ || tbl || $$
7441                 UNION ALL
7442             SELECT      *
7443               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7444     $$;
7445         RETURN TRUE;
7446 END;
7447 $creator$ LANGUAGE 'plpgsql';
7448
7449 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7450
7451 -- The main event
7452
7453 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7454 BEGIN
7455     PERFORM auditor.create_auditor_seq(sch, tbl);
7456     PERFORM auditor.create_auditor_history(sch, tbl);
7457     PERFORM auditor.create_auditor_func(sch, tbl);
7458     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7459     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7460     RETURN TRUE;
7461 END;
7462 $creator$ LANGUAGE 'plpgsql';
7463
7464 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7465
7466 ALTER TABLE action.hold_request
7467 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7468
7469 ALTER TABLE action.hold_request
7470 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7471
7472 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7473
7474 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7475
7476 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7477
7478 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7479
7480 -- Add claims_never_checked_out_count to actor.usr, related history
7481
7482 ALTER TABLE actor.usr ADD COLUMN
7483         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7484
7485 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7486         claims_never_checked_out_count INT;
7487
7488 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7489
7490 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7491
7492 -----------
7493
7494 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7495 BEGIN
7496         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7497                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7498                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7499                 END IF;
7500                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7501                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7502                 END IF;
7503                 IF NEW.stop_fines = 'LOST' THEN
7504                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7505                 END IF;
7506         END IF;
7507         RETURN NEW;
7508 END;
7509 $$ LANGUAGE 'plpgsql';
7510
7511 -- Create new table acq.fund_allocation_percent
7512 -- Populate it from acq.fund_allocation
7513 -- Convert all percentages to amounts in acq.fund_allocation
7514
7515 CREATE TABLE acq.fund_allocation_percent
7516 (
7517     id                   SERIAL            PRIMARY KEY,
7518     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7519                                                DEFERRABLE INITIALLY DEFERRED,
7520     org                  INT               NOT NULL REFERENCES actor.org_unit
7521                                                DEFERRABLE INITIALLY DEFERRED,
7522     fund_code            TEXT,
7523     percent              NUMERIC           NOT NULL,
7524     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7525                                                DEFERRABLE INITIALLY DEFERRED,
7526     note                 TEXT,
7527     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7528     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7529     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7530 );
7531
7532 -- Trigger function to validate combination of org_unit and fund_code
7533
7534 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7535 RETURNS TRIGGER AS $$
7536 --
7537 DECLARE
7538 --
7539 dummy int := 0;
7540 --
7541 BEGIN
7542     SELECT
7543         1
7544     INTO
7545         dummy
7546     FROM
7547         acq.fund
7548     WHERE
7549         org = NEW.org
7550         AND code = NEW.fund_code
7551         LIMIT 1;
7552     --
7553     IF dummy = 1 then
7554         RETURN NEW;
7555     ELSE
7556         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7557     END IF;
7558 END;
7559 $$ LANGUAGE plpgsql;
7560
7561 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7562     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7563     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7564
7565 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7566 RETURNS TRIGGER AS $$
7567 DECLARE
7568 --
7569 total_percent numeric;
7570 --
7571 BEGIN
7572     SELECT
7573         sum( percent )
7574     INTO
7575         total_percent
7576     FROM
7577         acq.fund_allocation_percent AS fap
7578     WHERE
7579         fap.funding_source = NEW.funding_source;
7580     --
7581     IF total_percent > 100 THEN
7582         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7583             NEW.funding_source;
7584     ELSE
7585         RETURN NEW;
7586     END IF;
7587 END;
7588 $$ LANGUAGE plpgsql;
7589
7590 CREATE TRIGGER acqfap_limit_100_trig
7591     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7592     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7593
7594 -- Populate new table from acq.fund_allocation
7595
7596 INSERT INTO acq.fund_allocation_percent
7597 (
7598     funding_source,
7599     org,
7600     fund_code,
7601     percent,
7602     allocator,
7603     note,
7604     create_time
7605 )
7606     SELECT
7607         fa.funding_source,
7608         fund.org,
7609         fund.code,
7610         fa.percent,
7611         fa.allocator,
7612         fa.note,
7613         fa.create_time
7614     FROM
7615         acq.fund_allocation AS fa
7616             INNER JOIN acq.fund AS fund
7617                 ON ( fa.fund = fund.id )
7618     WHERE
7619         fa.percent is not null
7620     ORDER BY
7621         fund.org;
7622
7623 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7624
7625 -- Algorithm to apply to each funding source:
7626
7627 -- 1. Add up the credits.
7628 -- 2. Add up the percentages.
7629 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7630 --    fractional cents from the result.  This is the total amount to be allocated.
7631 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7632 --    fractional cents to get a preliminary amount.
7633 -- 5. Add up the preliminary amounts for all the allocations.
7634 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7635 --    number of residual cents (resulting from having dropped fractional cents) that
7636 --    must be distributed across the funds in order to make the total of the amounts
7637 --    match the total allocation.
7638 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7639 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7640 --    for each successive fund, until all the residual cents have been exhausted.
7641
7642 -- Result: the sum of the individual allocations now equals the total to be allocated,
7643 -- to the penny.  The individual amounts match the percentages as closely as possible,
7644 -- given the constraint that the total must match.
7645
7646 CREATE OR REPLACE FUNCTION acq.apply_percents()
7647 RETURNS VOID AS $$
7648 declare
7649 --
7650 tot              RECORD;
7651 fund             RECORD;
7652 tot_cents        INTEGER;
7653 src              INTEGER;
7654 id               INTEGER[];
7655 curr_id          INTEGER;
7656 pennies          NUMERIC[];
7657 curr_amount      NUMERIC;
7658 i                INTEGER;
7659 total_of_floors  INTEGER;
7660 total_percent    NUMERIC;
7661 total_allocation INTEGER;
7662 residue          INTEGER;
7663 --
7664 begin
7665         RAISE NOTICE 'Applying percents';
7666         FOR tot IN
7667                 SELECT
7668                         fsrc.funding_source,
7669                         sum( fsrc.amount ) AS total
7670                 FROM
7671                         acq.funding_source_credit AS fsrc
7672                 WHERE fsrc.funding_source IN
7673                         ( SELECT DISTINCT fa.funding_source
7674                           FROM acq.fund_allocation AS fa
7675                           WHERE fa.percent IS NOT NULL )
7676                 GROUP BY
7677                         fsrc.funding_source
7678         LOOP
7679                 tot_cents = floor( tot.total * 100 );
7680                 src = tot.funding_source;
7681                 RAISE NOTICE 'Funding source % total %',
7682                         src, tot_cents;
7683                 i := 0;
7684                 total_of_floors := 0;
7685                 total_percent := 0;
7686                 --
7687                 FOR fund in
7688                         SELECT
7689                                 fa.id,
7690                                 fa.percent,
7691                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7692                         FROM
7693                                 acq.fund_allocation AS fa
7694                         WHERE
7695                                 fa.funding_source = src
7696                                 AND fa.percent IS NOT NULL
7697                         ORDER BY
7698                                 mod( fa.percent * tot_cents / 100, 1 ),
7699                                 fa.fund,
7700                                 fa.id
7701                 LOOP
7702                         RAISE NOTICE '   %: %',
7703                                 fund.id,
7704                                 fund.floor_pennies;
7705                         i := i + 1;
7706                         id[i] = fund.id;
7707                         pennies[i] = fund.floor_pennies;
7708                         total_percent := total_percent + fund.percent;
7709                         total_of_floors := total_of_floors + pennies[i];
7710                 END LOOP;
7711                 total_allocation := floor( total_percent * tot_cents /100 );
7712                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7713                 residue := total_allocation - total_of_floors;
7714                 RAISE NOTICE 'Residue: %', residue;
7715                 --
7716                 -- Post the calculated amounts, revising as needed to
7717                 -- distribute the rounding error
7718                 --
7719                 WHILE i > 0 LOOP
7720                         IF residue > 0 THEN
7721                                 pennies[i] = pennies[i] + 1;
7722                                 residue := residue - 1;
7723                         END IF;
7724                         --
7725                         -- Post amount
7726                         --
7727                         curr_id     := id[i];
7728                         curr_amount := trunc( pennies[i] / 100, 2 );
7729                         --
7730                         UPDATE
7731                                 acq.fund_allocation AS fa
7732                         SET
7733                                 amount = curr_amount,
7734                                 percent = NULL
7735                         WHERE
7736                                 fa.id = curr_id;
7737                         --
7738                         RAISE NOTICE '   ID % and amount %',
7739                                 curr_id,
7740                                 curr_amount;
7741                         i = i - 1;
7742                 END LOOP;
7743         END LOOP;
7744 end;
7745 $$ LANGUAGE 'plpgsql';
7746
7747 -- Run the temporary function
7748
7749 select * from acq.apply_percents();
7750
7751 -- Drop the temporary function now that we're done with it
7752
7753 DROP FUNCTION IF EXISTS acq.apply_percents();
7754
7755 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7756
7757 -- If the following step fails, it's probably because there are still some non-null percent values in
7758 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7759 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7760 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7761 -- slipped in afterwards.
7762
7763 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7764 -- procedure acq.apply_percents() as defined above.
7765
7766 ALTER TABLE acq.fund_allocation
7767 ALTER COLUMN amount SET NOT NULL;
7768
7769 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7770     SELECT  fund,
7771             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7772     FROM acq.fund_allocation a
7773          JOIN acq.fund f ON (a.fund = f.id)
7774          JOIN acq.funding_source s ON (a.funding_source = s.id)
7775     GROUP BY 1;
7776
7777 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7778     SELECT  funding_source,
7779             SUM(a.amount)::NUMERIC(100,2) AS amount
7780     FROM  acq.fund_allocation a
7781     GROUP BY 1;
7782
7783 ALTER TABLE acq.fund_allocation
7784 DROP COLUMN percent;
7785
7786 CREATE TABLE asset.copy_location_order
7787 (
7788         id              SERIAL           PRIMARY KEY,
7789         location        INT              NOT NULL
7790                                              REFERENCES asset.copy_location
7791                                              ON DELETE CASCADE
7792                                              DEFERRABLE INITIALLY DEFERRED,
7793         org             INT              NOT NULL
7794                                              REFERENCES actor.org_unit
7795                                              ON DELETE CASCADE
7796                                              DEFERRABLE INITIALLY DEFERRED,
7797         position        INT              NOT NULL DEFAULT 0,
7798         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7799 );
7800
7801 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7802
7803 -- If you ran this before its most recent incarnation:
7804 -- delete from config.upgrade_log where version = '0328';
7805 -- alter table money.credit_card_payment drop column cc_name;
7806
7807 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7808 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7809
7810 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$
7811 DECLARE
7812     current_group    permission.grp_tree%ROWTYPE;
7813     user_object    actor.usr%ROWTYPE;
7814     item_object    asset.copy%ROWTYPE;
7815     cn_object    asset.call_number%ROWTYPE;
7816     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7817     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7818     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7819 BEGIN
7820     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7821     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7822     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7823     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7824     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7825
7826     LOOP 
7827         -- for each potential matchpoint for this ou and group ...
7828         FOR current_mp IN
7829             SELECT  m.*
7830               FROM  config.circ_matrix_matchpoint m
7831                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
7832                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
7833               WHERE m.grp = current_group.id
7834                     AND m.active
7835                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
7836                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
7837               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
7838                     CASE WHEN m.copy_owning_lib IS NOT NULL
7839                         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 )
7840                         ELSE 0
7841                     END +
7842                     CASE WHEN m.copy_circ_lib IS NOT NULL
7843                         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 )
7844                         ELSE 0
7845                     END +
7846                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
7847                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
7848                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
7849                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
7850                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
7851                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
7852                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
7853                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
7854                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
7855
7856             IF current_mp.is_renewal IS NOT NULL THEN
7857                 CONTINUE WHEN current_mp.is_renewal <> renewal;
7858             END IF;
7859
7860             IF current_mp.circ_modifier IS NOT NULL THEN
7861                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
7862             END IF;
7863
7864             IF current_mp.marc_type IS NOT NULL THEN
7865                 IF item_object.circ_as_type IS NOT NULL THEN
7866                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
7867                 ELSE
7868                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
7869                 END IF;
7870             END IF;
7871
7872             IF current_mp.marc_form IS NOT NULL THEN
7873                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
7874             END IF;
7875
7876             IF current_mp.marc_vr_format IS NOT NULL THEN
7877                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
7878             END IF;
7879
7880             IF current_mp.ref_flag IS NOT NULL THEN
7881                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
7882             END IF;
7883
7884             IF current_mp.juvenile_flag IS NOT NULL THEN
7885                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
7886             END IF;
7887
7888             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
7889                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
7890             END IF;
7891
7892             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
7893                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
7894             END IF;
7895
7896
7897             -- everything was undefined or matched
7898             matchpoint = current_mp;
7899
7900             EXIT WHEN matchpoint.id IS NOT NULL;
7901         END LOOP;
7902
7903         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
7904
7905         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
7906     END LOOP;
7907
7908     RETURN matchpoint;
7909 END;
7910 $func$ LANGUAGE plpgsql;
7911
7912 CREATE TYPE action.hold_stats AS (
7913     hold_count              INT,
7914     copy_count              INT,
7915     available_count         INT,
7916     total_copy_ratio        FLOAT,
7917     available_copy_ratio    FLOAT
7918 );
7919
7920 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
7921 DECLARE
7922     output          action.hold_stats%ROWTYPE;
7923     hold_count      INT := 0;
7924     copy_count      INT := 0;
7925     available_count INT := 0;
7926     hold_map_data   RECORD;
7927 BEGIN
7928
7929     output.hold_count := 0;
7930     output.copy_count := 0;
7931     output.available_count := 0;
7932
7933     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
7934       FROM  action.hold_copy_map m
7935             JOIN action.hold_request h ON (m.hold = h.id)
7936       WHERE m.target_copy = copy_id
7937             AND NOT h.frozen;
7938
7939     output.hold_count := hold_count;
7940
7941     IF output.hold_count > 0 THEN
7942         FOR hold_map_data IN
7943             SELECT  DISTINCT m.target_copy,
7944                     acp.status
7945               FROM  action.hold_copy_map m
7946                     JOIN asset.copy acp ON (m.target_copy = acp.id)
7947                     JOIN action.hold_request h ON (m.hold = h.id)
7948               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
7949         LOOP
7950             output.copy_count := output.copy_count + 1;
7951             IF hold_map_data.status IN (0,7,12) THEN
7952                 output.available_count := output.available_count + 1;
7953             END IF;
7954         END LOOP;
7955         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
7956         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
7957
7958     END IF;
7959
7960     RETURN output;
7961
7962 END;
7963 $func$ LANGUAGE PLPGSQL;
7964
7965 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
7966 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
7967
7968 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
7969
7970 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7971 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7972
7973 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
7974     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
7975     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
7976     copy_owning_lib
7977 );
7978
7979 -- Return the correct fail_part when the item can't be found
7980 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$
7981 DECLARE
7982     user_object        actor.usr%ROWTYPE;
7983     standing_penalty    config.standing_penalty%ROWTYPE;
7984     item_object        asset.copy%ROWTYPE;
7985     item_status_object    config.copy_status%ROWTYPE;
7986     item_location_object    asset.copy_location%ROWTYPE;
7987     result            action.matrix_test_result;
7988     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
7989     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
7990     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
7991     hold_ratio          action.hold_stats%ROWTYPE;
7992     penalty_type         TEXT;
7993     tmp_grp         INT;
7994     items_out        INT;
7995     context_org_list        INT[];
7996     done            BOOL := FALSE;
7997 BEGIN
7998     result.success := TRUE;
7999
8000     -- Fail if the user is BARRED
8001     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8002
8003     -- Fail if we couldn't find the user 
8004     IF user_object.id IS NULL THEN
8005         result.fail_part := 'no_user';
8006         result.success := FALSE;
8007         done := TRUE;
8008         RETURN NEXT result;
8009         RETURN;
8010     END IF;
8011
8012     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
8013
8014     -- Fail if we couldn't find the item 
8015     IF item_object.id IS NULL THEN
8016         result.fail_part := 'no_item';
8017         result.success := FALSE;
8018         done := TRUE;
8019         RETURN NEXT result;
8020         RETURN;
8021     END IF;
8022
8023     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
8024     result.matchpoint := circ_test.id;
8025
8026     -- Fail if we couldn't find a matchpoint
8027     IF result.matchpoint IS NULL THEN
8028         result.fail_part := 'no_matchpoint';
8029         result.success := FALSE;
8030         done := TRUE;
8031         RETURN NEXT result;
8032     END IF;
8033
8034     IF user_object.barred IS TRUE THEN
8035         result.fail_part := 'actor.usr.barred';
8036         result.success := FALSE;
8037         done := TRUE;
8038         RETURN NEXT result;
8039     END IF;
8040
8041     -- Fail if the item can't circulate
8042     IF item_object.circulate IS FALSE THEN
8043         result.fail_part := 'asset.copy.circulate';
8044         result.success := FALSE;
8045         done := TRUE;
8046         RETURN NEXT result;
8047     END IF;
8048
8049     -- Fail if the item isn't in a circulateable status on a non-renewal
8050     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
8051         result.fail_part := 'asset.copy.status';
8052         result.success := FALSE;
8053         done := TRUE;
8054         RETURN NEXT result;
8055     ELSIF renewal AND item_object.status <> 1 THEN
8056         result.fail_part := 'asset.copy.status';
8057         result.success := FALSE;
8058         done := TRUE;
8059         RETURN NEXT result;
8060     END IF;
8061
8062     -- Fail if the item can't circulate because of the shelving location
8063     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8064     IF item_location_object.circulate IS FALSE THEN
8065         result.fail_part := 'asset.copy_location.circulate';
8066         result.success := FALSE;
8067         done := TRUE;
8068         RETURN NEXT result;
8069     END IF;
8070
8071     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
8072
8073     -- Fail if the test is set to hard non-circulating
8074     IF circ_test.circulate IS FALSE THEN
8075         result.fail_part := 'config.circ_matrix_test.circulate';
8076         result.success := FALSE;
8077         done := TRUE;
8078         RETURN NEXT result;
8079     END IF;
8080
8081     -- Fail if the total copy-hold ratio is too low
8082     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
8083         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8084         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
8085             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8086             result.success := FALSE;
8087             done := TRUE;
8088             RETURN NEXT result;
8089         END IF;
8090     END IF;
8091
8092     -- Fail if the available copy-hold ratio is too low
8093     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
8094         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8095         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
8096             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8097             result.success := FALSE;
8098             done := TRUE;
8099             RETURN NEXT result;
8100         END IF;
8101     END IF;
8102
8103     IF renewal THEN
8104         penalty_type = '%RENEW%';
8105     ELSE
8106         penalty_type = '%CIRC%';
8107     END IF;
8108
8109     FOR standing_penalty IN
8110         SELECT  DISTINCT csp.*
8111           FROM  actor.usr_standing_penalty usp
8112                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8113           WHERE usr = match_user
8114                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8115                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8116                 AND csp.block_list LIKE penalty_type LOOP
8117
8118         result.fail_part := standing_penalty.name;
8119         result.success := FALSE;
8120         done := TRUE;
8121         RETURN NEXT result;
8122     END LOOP;
8123
8124     -- Fail if the user has too many items with specific circ_modifiers checked out
8125     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
8126         SELECT  INTO items_out COUNT(*)
8127           FROM  action.circulation circ
8128             JOIN asset.copy cp ON (cp.id = circ.target_copy)
8129           WHERE circ.usr = match_user
8130                AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
8131             AND circ.checkin_time IS NULL
8132             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8133             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);
8134         IF items_out >= out_by_circ_mod.items_out THEN
8135             result.fail_part := 'config.circ_matrix_circ_mod_test';
8136             result.success := FALSE;
8137             done := TRUE;
8138             RETURN NEXT result;
8139         END IF;
8140     END LOOP;
8141
8142     -- If we passed everything, return the successful matchpoint id
8143     IF NOT done THEN
8144         RETURN NEXT result;
8145     END IF;
8146
8147     RETURN;
8148 END;
8149 $func$ LANGUAGE plpgsql;
8150
8151 CREATE TABLE config.remote_account (
8152     id          SERIAL  PRIMARY KEY,
8153     label       TEXT    NOT NULL,
8154     host        TEXT    NOT NULL,   -- name or IP, :port optional
8155     username    TEXT,               -- optional, since we could default to $USER
8156     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
8157     account     TEXT,               -- aka profile or FTP "account" command
8158     path        TEXT,               -- aka directory
8159     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8160     last_activity TIMESTAMP WITH TIME ZONE
8161 );
8162
8163 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8164     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8165     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8166         vendcode    TEXT,
8167         vendacct    TEXT
8168
8169 ) INHERITS (config.remote_account);
8170
8171 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8172
8173 CREATE TABLE acq.claim_type (
8174         id             SERIAL           PRIMARY KEY,
8175         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8176                                                  DEFERRABLE INITIALLY DEFERRED,
8177         code           TEXT             NOT NULL,
8178         description    TEXT             NOT NULL,
8179         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8180 );
8181
8182 CREATE TABLE acq.claim (
8183         id             SERIAL           PRIMARY KEY,
8184         type           INT              NOT NULL REFERENCES acq.claim_type
8185                                                  DEFERRABLE INITIALLY DEFERRED,
8186         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8187                                                  DEFERRABLE INITIALLY DEFERRED
8188 );
8189
8190 CREATE TABLE acq.claim_policy (
8191         id              SERIAL       PRIMARY KEY,
8192         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8193                                      DEFERRABLE INITIALLY DEFERRED,
8194         name            TEXT         NOT NULL,
8195         description     TEXT         NOT NULL,
8196         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8197 );
8198
8199 -- Add a san column for EDI. 
8200 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8201
8202 ALTER TABLE acq.provider ADD COLUMN san INT;
8203
8204 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8205
8206 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8207 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8208
8209 ALTER TABLE acq.provider
8210         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8211
8212 ALTER TABLE acq.provider
8213         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8214
8215 ALTER TABLE acq.provider
8216         ADD COLUMN url TEXT;
8217
8218 ALTER TABLE acq.provider
8219         ADD COLUMN email TEXT;
8220
8221 ALTER TABLE acq.provider
8222         ADD COLUMN phone TEXT;
8223
8224 ALTER TABLE acq.provider
8225         ADD COLUMN fax_phone TEXT;
8226
8227 ALTER TABLE acq.provider
8228         ADD COLUMN default_claim_policy INT
8229                 REFERENCES acq.claim_policy
8230                 DEFERRABLE INITIALLY DEFERRED;
8231
8232 ALTER TABLE action.transit_copy
8233 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8234                                                          DEFERRABLE INITIALLY DEFERRED;
8235
8236 DROP SCHEMA IF EXISTS booking CASCADE;
8237
8238 CREATE SCHEMA booking;
8239
8240 CREATE TABLE booking.resource_type (
8241         id             SERIAL          PRIMARY KEY,
8242         name           TEXT            NOT NULL,
8243         fine_interval  INTERVAL,
8244         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
8245         owner          INT             NOT NULL
8246                                        REFERENCES actor.org_unit( id )
8247                                        DEFERRABLE INITIALLY DEFERRED,
8248         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
8249         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
8250     record         BIGINT          REFERENCES biblio.record_entry (id)
8251                                        DEFERRABLE INITIALLY DEFERRED,
8252     max_fine       NUMERIC(8,2),
8253     elbow_room     INTERVAL,
8254     CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
8255 );
8256
8257 CREATE TABLE booking.resource (
8258         id             SERIAL           PRIMARY KEY,
8259         owner          INT              NOT NULL
8260                                         REFERENCES actor.org_unit(id)
8261                                         DEFERRABLE INITIALLY DEFERRED,
8262         type           INT              NOT NULL
8263                                         REFERENCES booking.resource_type(id)
8264                                         DEFERRABLE INITIALLY DEFERRED,
8265         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
8266         barcode        TEXT             NOT NULL,
8267         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
8268         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8269         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8270         CONSTRAINT br_unique UNIQUE (owner, barcode)
8271 );
8272
8273 -- For non-catalog items: hijack barcode for name/description
8274
8275 CREATE TABLE booking.resource_attr (
8276         id              SERIAL          PRIMARY KEY,
8277         owner           INT             NOT NULL
8278                                         REFERENCES actor.org_unit(id)
8279                                         DEFERRABLE INITIALLY DEFERRED,
8280         name            TEXT            NOT NULL,
8281         resource_type   INT             NOT NULL
8282                                         REFERENCES booking.resource_type(id)
8283                                         ON DELETE CASCADE
8284                                         DEFERRABLE INITIALLY DEFERRED,
8285         required        BOOLEAN         NOT NULL DEFAULT FALSE,
8286         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
8287 );
8288
8289 CREATE TABLE booking.resource_attr_value (
8290         id               SERIAL         PRIMARY KEY,
8291         owner            INT            NOT NULL
8292                                         REFERENCES actor.org_unit(id)
8293                                         DEFERRABLE INITIALLY DEFERRED,
8294         attr             INT            NOT NULL
8295                                         REFERENCES booking.resource_attr(id)
8296                                         DEFERRABLE INITIALLY DEFERRED,
8297         valid_value      TEXT           NOT NULL,
8298         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
8299 );
8300
8301 CREATE TABLE booking.resource_attr_map (
8302         id               SERIAL         PRIMARY KEY,
8303         resource         INT            NOT NULL
8304                                         REFERENCES booking.resource(id)
8305                                         ON DELETE CASCADE
8306                                         DEFERRABLE INITIALLY DEFERRED,
8307         resource_attr    INT            NOT NULL
8308                                         REFERENCES booking.resource_attr(id)
8309                                         ON DELETE CASCADE
8310                                         DEFERRABLE INITIALLY DEFERRED,
8311         value            INT            NOT NULL
8312                                         REFERENCES booking.resource_attr_value(id)
8313                                         DEFERRABLE INITIALLY DEFERRED,
8314         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
8315 );
8316
8317 CREATE TABLE booking.reservation (
8318         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
8319         start_time       TIMESTAMPTZ,
8320         end_time         TIMESTAMPTZ,
8321         capture_time     TIMESTAMPTZ,
8322         cancel_time      TIMESTAMPTZ,
8323         pickup_time      TIMESTAMPTZ,
8324         return_time      TIMESTAMPTZ,
8325         booking_interval INTERVAL,
8326         fine_interval    INTERVAL,
8327         fine_amount      DECIMAL(8,2),
8328         target_resource_type  INT       NOT NULL
8329                                         REFERENCES booking.resource_type(id)
8330                                         ON DELETE CASCADE
8331                                         DEFERRABLE INITIALLY DEFERRED,
8332         target_resource  INT            REFERENCES booking.resource(id)
8333                                         ON DELETE CASCADE
8334                                         DEFERRABLE INITIALLY DEFERRED,
8335         current_resource INT            REFERENCES booking.resource(id)
8336                                         ON DELETE CASCADE
8337                                         DEFERRABLE INITIALLY DEFERRED,
8338         request_lib      INT            NOT NULL
8339                                         REFERENCES actor.org_unit(id)
8340                                         DEFERRABLE INITIALLY DEFERRED,
8341         pickup_lib       INT            REFERENCES actor.org_unit(id)
8342                                         DEFERRABLE INITIALLY DEFERRED,
8343         capture_staff    INT            REFERENCES actor.usr(id)
8344                                         DEFERRABLE INITIALLY DEFERRED,
8345     max_fine         NUMERIC(8,2)
8346 ) INHERITS (money.billable_xact);
8347
8348 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
8349
8350 ALTER TABLE booking.reservation
8351         ADD CONSTRAINT booking_reservation_usr_fkey
8352         FOREIGN KEY (usr) REFERENCES actor.usr (id)
8353         DEFERRABLE INITIALLY DEFERRED;
8354
8355 CREATE TABLE booking.reservation_attr_value_map (
8356         id               SERIAL         PRIMARY KEY,
8357         reservation      INT            NOT NULL
8358                                         REFERENCES booking.reservation(id)
8359                                         ON DELETE CASCADE
8360                                         DEFERRABLE INITIALLY DEFERRED,
8361         attr_value       INT            NOT NULL
8362                                         REFERENCES booking.resource_attr_value(id)
8363                                         ON DELETE CASCADE
8364                                         DEFERRABLE INITIALLY DEFERRED,
8365         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
8366 );
8367
8368 -- represents a circ chain summary
8369 CREATE TYPE action.circ_chain_summary AS (
8370     num_circs INTEGER,
8371     start_time TIMESTAMP WITH TIME ZONE,
8372     checkout_workstation TEXT,
8373     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8374     last_stop_fines TEXT,
8375     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8376     last_renewal_workstation TEXT, -- NULL if no renewals
8377     last_checkin_workstation TEXT,
8378     last_checkin_time TIMESTAMP WITH TIME ZONE,
8379     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8380 );
8381
8382 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8383 DECLARE
8384     tmp_circ action.circulation%ROWTYPE;
8385     circ_0 action.circulation%ROWTYPE;
8386 BEGIN
8387
8388     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8389
8390     IF tmp_circ IS NULL THEN
8391         RETURN NEXT tmp_circ;
8392     END IF;
8393     circ_0 := tmp_circ;
8394
8395     -- find the front of the chain
8396     WHILE TRUE LOOP
8397         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8398         IF tmp_circ IS NULL THEN
8399             EXIT;
8400         END IF;
8401         circ_0 := tmp_circ;
8402     END LOOP;
8403
8404     -- now send the circs to the caller, oldest to newest
8405     tmp_circ := circ_0;
8406     WHILE TRUE LOOP
8407         IF tmp_circ IS NULL THEN
8408             EXIT;
8409         END IF;
8410         RETURN NEXT tmp_circ;
8411         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8412     END LOOP;
8413
8414 END;
8415 $$ LANGUAGE 'plpgsql';
8416
8417 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8418
8419 DECLARE
8420
8421     -- first circ in the chain
8422     circ_0 action.circulation%ROWTYPE;
8423
8424     -- last circ in the chain
8425     circ_n action.circulation%ROWTYPE;
8426
8427     -- circ chain under construction
8428     chain action.circ_chain_summary;
8429     tmp_circ action.circulation%ROWTYPE;
8430
8431 BEGIN
8432     
8433     chain.num_circs := 0;
8434     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8435
8436         IF chain.num_circs = 0 THEN
8437             circ_0 := tmp_circ;
8438         END IF;
8439
8440         chain.num_circs := chain.num_circs + 1;
8441         circ_n := tmp_circ;
8442     END LOOP;
8443
8444     chain.start_time := circ_0.xact_start;
8445     chain.last_stop_fines := circ_n.stop_fines;
8446     chain.last_stop_fines_time := circ_n.stop_fines_time;
8447     chain.last_checkin_time := circ_n.checkin_time;
8448     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8449     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8450     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8451
8452     IF chain.num_circs > 1 THEN
8453         chain.last_renewal_time := circ_n.xact_start;
8454         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8455     END IF;
8456
8457     RETURN chain;
8458
8459 END;
8460 $$ LANGUAGE 'plpgsql';
8461
8462 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8463 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8464 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8465
8466 ALTER TABLE config.standing_penalty
8467         ADD COLUMN org_depth   INTEGER;
8468
8469 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8470 DECLARE
8471     user_object         actor.usr%ROWTYPE;
8472     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8473     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8474     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8475     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8476     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8477     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8478     tmp_grp             INT;
8479     items_overdue       INT;
8480     items_out           INT;
8481     context_org_list    INT[];
8482     current_fines        NUMERIC(8,2) := 0.0;
8483     tmp_fines            NUMERIC(8,2);
8484     tmp_groc            RECORD;
8485     tmp_circ            RECORD;
8486     tmp_org             actor.org_unit%ROWTYPE;
8487     tmp_penalty         config.standing_penalty%ROWTYPE;
8488     tmp_depth           INTEGER;
8489 BEGIN
8490     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8491
8492     -- Max fines
8493     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8494
8495     -- Fail if the user has a high fine balance
8496     LOOP
8497         tmp_grp := user_object.profile;
8498         LOOP
8499             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8500
8501             IF max_fines.threshold IS NULL THEN
8502                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8503             ELSE
8504                 EXIT;
8505             END IF;
8506
8507             IF tmp_grp IS NULL THEN
8508                 EXIT;
8509             END IF;
8510         END LOOP;
8511
8512         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8513             EXIT;
8514         END IF;
8515
8516         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8517
8518     END LOOP;
8519
8520     IF max_fines.threshold IS NOT NULL THEN
8521
8522         RETURN QUERY
8523             SELECT  *
8524               FROM  actor.usr_standing_penalty
8525               WHERE usr = match_user
8526                     AND org_unit = max_fines.org_unit
8527                     AND (stop_date IS NULL or stop_date > NOW())
8528                     AND standing_penalty = 1;
8529
8530         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8531
8532         SELECT  SUM(f.balance_owed) INTO current_fines
8533           FROM  money.materialized_billable_xact_summary f
8534                 JOIN (
8535                     SELECT  r.id
8536                       FROM  booking.reservation r
8537                       WHERE r.usr = match_user
8538                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8539                             AND xact_finish IS NULL
8540                                 UNION ALL
8541                     SELECT  g.id
8542                       FROM  money.grocery g
8543                       WHERE g.usr = match_user
8544                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8545                             AND xact_finish IS NULL
8546                                 UNION ALL
8547                     SELECT  circ.id
8548                       FROM  action.circulation circ
8549                       WHERE circ.usr = match_user
8550                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8551                             AND xact_finish IS NULL ) l USING (id);
8552
8553         IF current_fines >= max_fines.threshold THEN
8554             new_sp_row.usr := match_user;
8555             new_sp_row.org_unit := max_fines.org_unit;
8556             new_sp_row.standing_penalty := 1;
8557             RETURN NEXT new_sp_row;
8558         END IF;
8559     END IF;
8560
8561     -- Start over for max overdue
8562     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8563
8564     -- Fail if the user has too many overdue items
8565     LOOP
8566         tmp_grp := user_object.profile;
8567         LOOP
8568
8569             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8570
8571             IF max_overdue.threshold IS NULL THEN
8572                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8573             ELSE
8574                 EXIT;
8575             END IF;
8576
8577             IF tmp_grp IS NULL THEN
8578                 EXIT;
8579             END IF;
8580         END LOOP;
8581
8582         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8583             EXIT;
8584         END IF;
8585
8586         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8587
8588     END LOOP;
8589
8590     IF max_overdue.threshold IS NOT NULL THEN
8591
8592         RETURN QUERY
8593             SELECT  *
8594               FROM  actor.usr_standing_penalty
8595               WHERE usr = match_user
8596                     AND org_unit = max_overdue.org_unit
8597                     AND (stop_date IS NULL or stop_date > NOW())
8598                     AND standing_penalty = 2;
8599
8600         SELECT  INTO items_overdue COUNT(*)
8601           FROM  action.circulation circ
8602                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8603           WHERE circ.usr = match_user
8604             AND circ.checkin_time IS NULL
8605             AND circ.due_date < NOW()
8606             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8607
8608         IF items_overdue >= max_overdue.threshold::INT THEN
8609             new_sp_row.usr := match_user;
8610             new_sp_row.org_unit := max_overdue.org_unit;
8611             new_sp_row.standing_penalty := 2;
8612             RETURN NEXT new_sp_row;
8613         END IF;
8614     END IF;
8615
8616     -- Start over for max out
8617     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8618
8619     -- Fail if the user has too many checked out items
8620     LOOP
8621         tmp_grp := user_object.profile;
8622         LOOP
8623             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8624
8625             IF max_items_out.threshold IS NULL THEN
8626                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8627             ELSE
8628                 EXIT;
8629             END IF;
8630
8631             IF tmp_grp IS NULL THEN
8632                 EXIT;
8633             END IF;
8634         END LOOP;
8635
8636         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8637             EXIT;
8638         END IF;
8639
8640         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8641
8642     END LOOP;
8643
8644
8645     -- Fail if the user has too many items checked out
8646     IF max_items_out.threshold IS NOT NULL THEN
8647
8648         RETURN QUERY
8649             SELECT  *
8650               FROM  actor.usr_standing_penalty
8651               WHERE usr = match_user
8652                     AND org_unit = max_items_out.org_unit
8653                     AND (stop_date IS NULL or stop_date > NOW())
8654                     AND standing_penalty = 3;
8655
8656         SELECT  INTO items_out COUNT(*)
8657           FROM  action.circulation circ
8658                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8659           WHERE circ.usr = match_user
8660                 AND circ.checkin_time IS NULL
8661                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8662
8663            IF items_out >= max_items_out.threshold::INT THEN
8664             new_sp_row.usr := match_user;
8665             new_sp_row.org_unit := max_items_out.org_unit;
8666             new_sp_row.standing_penalty := 3;
8667             RETURN NEXT new_sp_row;
8668            END IF;
8669     END IF;
8670
8671     -- Start over for collections warning
8672     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8673
8674     -- Fail if the user has a collections-level fine balance
8675     LOOP
8676         tmp_grp := user_object.profile;
8677         LOOP
8678             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8679
8680             IF max_fines.threshold IS NULL THEN
8681                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8682             ELSE
8683                 EXIT;
8684             END IF;
8685
8686             IF tmp_grp IS NULL THEN
8687                 EXIT;
8688             END IF;
8689         END LOOP;
8690
8691         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8692             EXIT;
8693         END IF;
8694
8695         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8696
8697     END LOOP;
8698
8699     IF max_fines.threshold IS NOT NULL THEN
8700
8701         RETURN QUERY
8702             SELECT  *
8703               FROM  actor.usr_standing_penalty
8704               WHERE usr = match_user
8705                     AND org_unit = max_fines.org_unit
8706                     AND (stop_date IS NULL or stop_date > NOW())
8707                     AND standing_penalty = 4;
8708
8709         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8710
8711         SELECT  SUM(f.balance_owed) INTO current_fines
8712           FROM  money.materialized_billable_xact_summary f
8713                 JOIN (
8714                     SELECT  r.id
8715                       FROM  booking.reservation r
8716                       WHERE r.usr = match_user
8717                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8718                             AND r.xact_finish IS NULL
8719                                 UNION ALL
8720                     SELECT  g.id
8721                       FROM  money.grocery g
8722                       WHERE g.usr = match_user
8723                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8724                             AND g.xact_finish IS NULL
8725                                 UNION ALL
8726                     SELECT  circ.id
8727                       FROM  action.circulation circ
8728                       WHERE circ.usr = match_user
8729                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8730                             AND circ.xact_finish IS NULL ) l USING (id);
8731
8732         IF current_fines >= max_fines.threshold THEN
8733             new_sp_row.usr := match_user;
8734             new_sp_row.org_unit := max_fines.org_unit;
8735             new_sp_row.standing_penalty := 4;
8736             RETURN NEXT new_sp_row;
8737         END IF;
8738     END IF;
8739
8740     -- Start over for in collections
8741     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8742
8743     -- Remove the in-collections penalty if the user has paid down enough
8744     -- This penalty is different, because this code is not responsible for creating 
8745     -- new in-collections penalties, only for removing them
8746     LOOP
8747         tmp_grp := user_object.profile;
8748         LOOP
8749             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8750
8751             IF max_fines.threshold IS NULL THEN
8752                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8753             ELSE
8754                 EXIT;
8755             END IF;
8756
8757             IF tmp_grp IS NULL THEN
8758                 EXIT;
8759             END IF;
8760         END LOOP;
8761
8762         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8763             EXIT;
8764         END IF;
8765
8766         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8767
8768     END LOOP;
8769
8770     IF max_fines.threshold IS NOT NULL THEN
8771
8772         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8773
8774         -- first, see if the user had paid down to the threshold
8775         SELECT  SUM(f.balance_owed) INTO current_fines
8776           FROM  money.materialized_billable_xact_summary f
8777                 JOIN (
8778                     SELECT  r.id
8779                       FROM  booking.reservation r
8780                       WHERE r.usr = match_user
8781                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8782                             AND r.xact_finish IS NULL
8783                                 UNION ALL
8784                     SELECT  g.id
8785                       FROM  money.grocery g
8786                       WHERE g.usr = match_user
8787                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8788                             AND g.xact_finish IS NULL
8789                                 UNION ALL
8790                     SELECT  circ.id
8791                       FROM  action.circulation circ
8792                       WHERE circ.usr = match_user
8793                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8794                             AND circ.xact_finish IS NULL ) l USING (id);
8795
8796         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8797             -- patron has paid down enough
8798
8799             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8800
8801             IF tmp_penalty.org_depth IS NOT NULL THEN
8802
8803                 -- since this code is not responsible for applying the penalty, it can't 
8804                 -- guarantee the current context org will match the org at which the penalty 
8805                 --- was applied.  search up the org tree until we hit the configured penalty depth
8806                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8807                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8808
8809                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8810
8811                     RETURN QUERY
8812                         SELECT  *
8813                           FROM  actor.usr_standing_penalty
8814                           WHERE usr = match_user
8815                                 AND org_unit = tmp_org.id
8816                                 AND (stop_date IS NULL or stop_date > NOW())
8817                                 AND standing_penalty = 30;
8818
8819                     IF tmp_org.parent_ou IS NULL THEN
8820                         EXIT;
8821                     END IF;
8822
8823                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8824                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8825                 END LOOP;
8826
8827             ELSE
8828
8829                 -- no penalty depth is defined, look for exact matches
8830
8831                 RETURN QUERY
8832                     SELECT  *
8833                       FROM  actor.usr_standing_penalty
8834                       WHERE usr = match_user
8835                             AND org_unit = max_fines.org_unit
8836                             AND (stop_date IS NULL or stop_date > NOW())
8837                             AND standing_penalty = 30;
8838             END IF;
8839     
8840         END IF;
8841
8842     END IF;
8843
8844     RETURN;
8845 END;
8846 $func$ LANGUAGE plpgsql;
8847
8848 -- Create a default row in acq.fiscal_calendar
8849 -- Add a column in actor.org_unit to point to it
8850
8851 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8852
8853 ALTER TABLE actor.org_unit
8854 ADD COLUMN fiscal_calendar INT NOT NULL
8855         REFERENCES acq.fiscal_calendar( id )
8856         DEFERRABLE INITIALLY DEFERRED
8857         DEFAULT 1;
8858
8859 ALTER TABLE auditor.actor_org_unit_history
8860         ADD COLUMN fiscal_calendar INT;
8861
8862 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8863
8864 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8865
8866 ALTER TABLE acq.funding_source_credit
8867 ADD COLUMN deadline_date TIMESTAMPTZ;
8868
8869 ALTER TABLE acq.funding_source_credit
8870 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8871
8872 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8873
8874 CREATE TABLE acq.fund_transfer (
8875         id               SERIAL         PRIMARY KEY,
8876         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8877                                         DEFERRABLE INITIALLY DEFERRED,
8878         src_amount       NUMERIC        NOT NULL,
8879         dest_fund        INT            REFERENCES acq.fund( id )
8880                                         DEFERRABLE INITIALLY DEFERRED,
8881         dest_amount      NUMERIC,
8882         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
8883         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
8884                                         DEFERRABLE INITIALLY DEFERRED,
8885         note             TEXT,
8886     funding_source_credit INTEGER   NOT NULL
8887                                         REFERENCES acq.funding_source_credit(id)
8888                                         DEFERRABLE INITIALLY DEFERRED
8889 );
8890
8891 CREATE INDEX acqftr_usr_idx
8892 ON acq.fund_transfer( transfer_user );
8893
8894 COMMENT ON TABLE acq.fund_transfer IS $$
8895 /*
8896  * Copyright (C) 2009  Georgia Public Library Service
8897  * Scott McKellar <scott@esilibrary.com>
8898  *
8899  * Fund Transfer
8900  *
8901  * Each row represents the transfer of money from a source fund
8902  * to a destination fund.  There should be corresponding entries
8903  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
8904  * to record how much money moved from which fund to which other
8905  * fund.
8906  * 
8907  * The presence of two amount fields, rather than one, reflects
8908  * the possibility that the two funds are denominated in different
8909  * currencies.  If they use the same currency type, the two
8910  * amounts should be the same.
8911  *
8912  * ****
8913  *
8914  * This program is free software; you can redistribute it and/or
8915  * modify it under the terms of the GNU General Public License
8916  * as published by the Free Software Foundation; either version 2
8917  * of the License, or (at your option) any later version.
8918  *
8919  * This program is distributed in the hope that it will be useful,
8920  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8921  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
8922  * GNU General Public License for more details.
8923  */
8924 $$;
8925
8926 CREATE TABLE acq.claim_event_type (
8927         id             SERIAL           PRIMARY KEY,
8928         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8929                                                  DEFERRABLE INITIALLY DEFERRED,
8930         code           TEXT             NOT NULL,
8931         description    TEXT             NOT NULL,
8932         library_initiated BOOL          NOT NULL DEFAULT FALSE,
8933         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
8934 );
8935
8936 CREATE TABLE acq.claim_event (
8937         id             BIGSERIAL        PRIMARY KEY,
8938         type           INT              NOT NULL REFERENCES acq.claim_event_type
8939                                                  DEFERRABLE INITIALLY DEFERRED,
8940         claim          SERIAL           NOT NULL REFERENCES acq.claim
8941                                                  DEFERRABLE INITIALLY DEFERRED,
8942         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
8943         creator        INT              NOT NULL REFERENCES actor.usr
8944                                                  DEFERRABLE INITIALLY DEFERRED,
8945         note           TEXT
8946 );
8947
8948 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
8949
8950 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
8951         src_usr  IN INTEGER,
8952         dest_usr IN INTEGER
8953 ) RETURNS VOID AS $$
8954 DECLARE
8955         suffix TEXT;
8956         renamable_row RECORD;
8957 BEGIN
8958
8959         UPDATE actor.usr SET
8960                 active = FALSE,
8961                 card = NULL,
8962                 mailing_address = NULL,
8963                 billing_address = NULL
8964         WHERE id = src_usr;
8965
8966         -- acq.*
8967         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
8968         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
8969         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
8970         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
8971         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
8972         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
8973         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
8974
8975         -- Update with a rename to avoid collisions
8976         FOR renamable_row in
8977                 SELECT id, name
8978                 FROM   acq.picklist
8979                 WHERE  owner = src_usr
8980         LOOP
8981                 suffix := ' (' || src_usr || ')';
8982                 LOOP
8983                         BEGIN
8984                                 UPDATE  acq.picklist
8985                                 SET     owner = dest_usr, name = name || suffix
8986                                 WHERE   id = renamable_row.id;
8987                         EXCEPTION WHEN unique_violation THEN
8988                                 suffix := suffix || ' ';
8989                                 CONTINUE;
8990                         END;
8991                         EXIT;
8992                 END LOOP;
8993         END LOOP;
8994
8995         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
8996         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
8997         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
8998         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
8999         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
9000         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
9001         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
9002         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
9003
9004         -- action.*
9005         DELETE FROM action.circulation WHERE usr = src_usr;
9006         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
9007         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
9008         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
9009         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
9010         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
9011         DELETE FROM action.hold_request WHERE usr = src_usr;
9012         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
9013         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
9014         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
9015         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
9016         DELETE FROM action.survey_response WHERE usr = src_usr;
9017         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
9018
9019         -- actor.*
9020         DELETE FROM actor.card WHERE usr = src_usr;
9021         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
9022
9023         -- The following update is intended to avoid transient violations of a foreign
9024         -- key constraint, whereby actor.usr_address references itself.  It may not be
9025         -- necessary, but it does no harm.
9026         UPDATE actor.usr_address SET replaces = NULL
9027                 WHERE usr = src_usr AND replaces IS NOT NULL;
9028         DELETE FROM actor.usr_address WHERE usr = src_usr;
9029         DELETE FROM actor.usr_note WHERE usr = src_usr;
9030         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
9031         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
9032         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
9033         DELETE FROM actor.usr_setting WHERE usr = src_usr;
9034         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
9035         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
9036
9037         -- asset.*
9038         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
9039         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
9040         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
9041         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
9042         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
9043         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
9044
9045         -- auditor.*
9046         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
9047         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
9048         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
9049         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
9050         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
9051         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
9052         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
9053         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
9054
9055         -- biblio.*
9056         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
9057         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
9058         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
9059         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
9060
9061         -- container.*
9062         -- Update buckets with a rename to avoid collisions
9063         FOR renamable_row in
9064                 SELECT id, name
9065                 FROM   container.biblio_record_entry_bucket
9066                 WHERE  owner = src_usr
9067         LOOP
9068                 suffix := ' (' || src_usr || ')';
9069                 LOOP
9070                         BEGIN
9071                                 UPDATE  container.biblio_record_entry_bucket
9072                                 SET     owner = dest_usr, name = name || suffix
9073                                 WHERE   id = renamable_row.id;
9074                         EXCEPTION WHEN unique_violation THEN
9075                                 suffix := suffix || ' ';
9076                                 CONTINUE;
9077                         END;
9078                         EXIT;
9079                 END LOOP;
9080         END LOOP;
9081
9082         FOR renamable_row in
9083                 SELECT id, name
9084                 FROM   container.call_number_bucket
9085                 WHERE  owner = src_usr
9086         LOOP
9087                 suffix := ' (' || src_usr || ')';
9088                 LOOP
9089                         BEGIN
9090                                 UPDATE  container.call_number_bucket
9091                                 SET     owner = dest_usr, name = name || suffix
9092                                 WHERE   id = renamable_row.id;
9093                         EXCEPTION WHEN unique_violation THEN
9094                                 suffix := suffix || ' ';
9095                                 CONTINUE;
9096                         END;
9097                         EXIT;
9098                 END LOOP;
9099         END LOOP;
9100
9101         FOR renamable_row in
9102                 SELECT id, name
9103                 FROM   container.copy_bucket
9104                 WHERE  owner = src_usr
9105         LOOP
9106                 suffix := ' (' || src_usr || ')';
9107                 LOOP
9108                         BEGIN
9109                                 UPDATE  container.copy_bucket
9110                                 SET     owner = dest_usr, name = name || suffix
9111                                 WHERE   id = renamable_row.id;
9112                         EXCEPTION WHEN unique_violation THEN
9113                                 suffix := suffix || ' ';
9114                                 CONTINUE;
9115                         END;
9116                         EXIT;
9117                 END LOOP;
9118         END LOOP;
9119
9120         FOR renamable_row in
9121                 SELECT id, name
9122                 FROM   container.user_bucket
9123                 WHERE  owner = src_usr
9124         LOOP
9125                 suffix := ' (' || src_usr || ')';
9126                 LOOP
9127                         BEGIN
9128                                 UPDATE  container.user_bucket
9129                                 SET     owner = dest_usr, name = name || suffix
9130                                 WHERE   id = renamable_row.id;
9131                         EXCEPTION WHEN unique_violation THEN
9132                                 suffix := suffix || ' ';
9133                                 CONTINUE;
9134                         END;
9135                         EXIT;
9136                 END LOOP;
9137         END LOOP;
9138
9139         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
9140
9141         -- money.*
9142         DELETE FROM money.billable_xact WHERE usr = src_usr;
9143         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9144         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9145
9146         -- permission.*
9147         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9148         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9149         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9150         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9151
9152         -- reporter.*
9153         -- Update with a rename to avoid collisions
9154         BEGIN
9155                 FOR renamable_row in
9156                         SELECT id, name
9157                         FROM   reporter.output_folder
9158                         WHERE  owner = src_usr
9159                 LOOP
9160                         suffix := ' (' || src_usr || ')';
9161                         LOOP
9162                                 BEGIN
9163                                         UPDATE  reporter.output_folder
9164                                         SET     owner = dest_usr, name = name || suffix
9165                                         WHERE   id = renamable_row.id;
9166                                 EXCEPTION WHEN unique_violation THEN
9167                                         suffix := suffix || ' ';
9168                                         CONTINUE;
9169                                 END;
9170                                 EXIT;
9171                         END LOOP;
9172                 END LOOP;
9173         EXCEPTION WHEN undefined_table THEN
9174                 -- do nothing
9175         END;
9176
9177         BEGIN
9178                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9179         EXCEPTION WHEN undefined_table THEN
9180                 -- do nothing
9181         END;
9182
9183         -- Update with a rename to avoid collisions
9184         BEGIN
9185                 FOR renamable_row in
9186                         SELECT id, name
9187                         FROM   reporter.report_folder
9188                         WHERE  owner = src_usr
9189                 LOOP
9190                         suffix := ' (' || src_usr || ')';
9191                         LOOP
9192                                 BEGIN
9193                                         UPDATE  reporter.report_folder
9194                                         SET     owner = dest_usr, name = name || suffix
9195                                         WHERE   id = renamable_row.id;
9196                                 EXCEPTION WHEN unique_violation THEN
9197                                         suffix := suffix || ' ';
9198                                         CONTINUE;
9199                                 END;
9200                                 EXIT;
9201                         END LOOP;
9202                 END LOOP;
9203         EXCEPTION WHEN undefined_table THEN
9204                 -- do nothing
9205         END;
9206
9207         BEGIN
9208                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9209         EXCEPTION WHEN undefined_table THEN
9210                 -- do nothing
9211         END;
9212
9213         BEGIN
9214                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9215         EXCEPTION WHEN undefined_table THEN
9216                 -- do nothing
9217         END;
9218
9219         -- Update with a rename to avoid collisions
9220         BEGIN
9221                 FOR renamable_row in
9222                         SELECT id, name
9223                         FROM   reporter.template_folder
9224                         WHERE  owner = src_usr
9225                 LOOP
9226                         suffix := ' (' || src_usr || ')';
9227                         LOOP
9228                                 BEGIN
9229                                         UPDATE  reporter.template_folder
9230                                         SET     owner = dest_usr, name = name || suffix
9231                                         WHERE   id = renamable_row.id;
9232                                 EXCEPTION WHEN unique_violation THEN
9233                                         suffix := suffix || ' ';
9234                                         CONTINUE;
9235                                 END;
9236                                 EXIT;
9237                         END LOOP;
9238                 END LOOP;
9239         EXCEPTION WHEN undefined_table THEN
9240         -- do nothing
9241         END;
9242
9243         -- vandelay.*
9244         -- Update with a rename to avoid collisions
9245         FOR renamable_row in
9246                 SELECT id, name
9247                 FROM   vandelay.queue
9248                 WHERE  owner = src_usr
9249         LOOP
9250                 suffix := ' (' || src_usr || ')';
9251                 LOOP
9252                         BEGIN
9253                                 UPDATE  vandelay.queue
9254                                 SET     owner = dest_usr, name = name || suffix
9255                                 WHERE   id = renamable_row.id;
9256                         EXCEPTION WHEN unique_violation THEN
9257                                 suffix := suffix || ' ';
9258                                 CONTINUE;
9259                         END;
9260                         EXIT;
9261                 END LOOP;
9262         END LOOP;
9263
9264 END;
9265 $$ LANGUAGE plpgsql;
9266
9267 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9268 /**
9269  * Finds rows dependent on a given row in actor.usr and either deletes them
9270  * or reassigns them to a different user.
9271  */
9272 $$;
9273
9274 CREATE OR REPLACE FUNCTION actor.usr_delete(
9275         src_usr  IN INTEGER,
9276         dest_usr IN INTEGER
9277 ) RETURNS VOID AS $$
9278 DECLARE
9279         old_profile actor.usr.profile%type;
9280         old_home_ou actor.usr.home_ou%type;
9281         new_profile actor.usr.profile%type;
9282         new_home_ou actor.usr.home_ou%type;
9283         new_name    text;
9284         new_dob     actor.usr.dob%type;
9285 BEGIN
9286         SELECT
9287                 id || '-PURGED-' || now(),
9288                 profile,
9289                 home_ou,
9290                 dob
9291         INTO
9292                 new_name,
9293                 old_profile,
9294                 old_home_ou,
9295                 new_dob
9296         FROM
9297                 actor.usr
9298         WHERE
9299                 id = src_usr;
9300         --
9301         -- Quit if no such user
9302         --
9303         IF old_profile IS NULL THEN
9304                 RETURN;
9305         END IF;
9306         --
9307         perform actor.usr_purge_data( src_usr, dest_usr );
9308         --
9309         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9310         -- could assume that there is only one root.  Theoretically, someday, maybe,
9311         -- there could be multiple roots, so we take extra trouble to get the right ones.
9312         --
9313         SELECT
9314                 id
9315         INTO
9316                 new_profile
9317         FROM
9318                 permission.grp_ancestors( old_profile )
9319         WHERE
9320                 parent is null;
9321         --
9322         SELECT
9323                 id
9324         INTO
9325                 new_home_ou
9326         FROM
9327                 actor.org_unit_ancestors( old_home_ou )
9328         WHERE
9329                 parent_ou is null;
9330         --
9331         -- Truncate date of birth
9332         --
9333         IF new_dob IS NOT NULL THEN
9334                 new_dob := date_trunc( 'year', new_dob );
9335         END IF;
9336         --
9337         UPDATE
9338                 actor.usr
9339                 SET
9340                         card = NULL,
9341                         profile = new_profile,
9342                         usrname = new_name,
9343                         email = NULL,
9344                         passwd = random()::text,
9345                         standing = DEFAULT,
9346                         ident_type = 
9347                         (
9348                                 SELECT MIN( id )
9349                                 FROM config.identification_type
9350                         ),
9351                         ident_value = NULL,
9352                         ident_type2 = NULL,
9353                         ident_value2 = NULL,
9354                         net_access_level = DEFAULT,
9355                         photo_url = NULL,
9356                         prefix = NULL,
9357                         first_given_name = new_name,
9358                         second_given_name = NULL,
9359                         family_name = new_name,
9360                         suffix = NULL,
9361                         alias = NULL,
9362                         day_phone = NULL,
9363                         evening_phone = NULL,
9364                         other_phone = NULL,
9365                         mailing_address = NULL,
9366                         billing_address = NULL,
9367                         home_ou = new_home_ou,
9368                         dob = new_dob,
9369                         active = FALSE,
9370                         master_account = DEFAULT, 
9371                         super_user = DEFAULT,
9372                         barred = FALSE,
9373                         deleted = TRUE,
9374                         juvenile = DEFAULT,
9375                         usrgroup = 0,
9376                         claims_returned_count = DEFAULT,
9377                         credit_forward_balance = DEFAULT,
9378                         last_xact_id = DEFAULT,
9379                         alert_message = NULL,
9380                         create_date = now(),
9381                         expire_date = now()
9382         WHERE
9383                 id = src_usr;
9384 END;
9385 $$ LANGUAGE plpgsql;
9386
9387 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9388 /**
9389  * Logically deletes a user.  Removes personally identifiable information,
9390  * and purges associated data in other tables.
9391  */
9392 $$;
9393
9394 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9395
9396 ALTER TABLE acq.fund
9397 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9398
9399 ALTER TABLE acq.fund
9400         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9401
9402 -- A fund can't roll over if it doesn't propagate from one year to the next
9403
9404 ALTER TABLE acq.fund
9405         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9406         ( propagate OR NOT rollover );
9407
9408 ALTER TABLE acq.fund
9409         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9410
9411 ALTER TABLE acq.fund
9412     ADD COLUMN balance_warning_percent INT;
9413
9414 ALTER TABLE acq.fund
9415     ADD COLUMN balance_stop_percent INT;
9416
9417 CREATE VIEW acq.ordered_funding_source_credit AS
9418         SELECT
9419                 CASE WHEN deadline_date IS NULL THEN
9420                         2
9421                 ELSE
9422                         1
9423                 END AS sort_priority,
9424                 CASE WHEN deadline_date IS NULL THEN
9425                         effective_date
9426                 ELSE
9427                         deadline_date
9428                 END AS sort_date,
9429                 id,
9430                 funding_source,
9431                 amount,
9432                 note
9433         FROM
9434                 acq.funding_source_credit;
9435
9436 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9437 /*
9438  * Copyright (C) 2009  Georgia Public Library Service
9439  * Scott McKellar <scott@gmail.com>
9440  *
9441  * The acq.ordered_funding_source_credit view is a prioritized
9442  * ordering of funding source credits.  When ordered by the first
9443  * three columns, this view defines the order in which the various
9444  * credits are to be tapped for spending, subject to the allocations
9445  * in the acq.fund_allocation table.
9446  *
9447  * The first column reflects the principle that we should spend
9448  * money with deadlines before spending money without deadlines.
9449  *
9450  * The second column reflects the principle that we should spend the
9451  * oldest money first.  For money with deadlines, that means that we
9452  * spend first from the credit with the earliest deadline.  For
9453  * money without deadlines, we spend first from the credit with the
9454  * earliest effective date.  
9455  *
9456  * The third column is a tie breaker to ensure a consistent
9457  * ordering.
9458  *
9459  * ****
9460  *
9461  * This program is free software; you can redistribute it and/or
9462  * modify it under the terms of the GNU General Public License
9463  * as published by the Free Software Foundation; either version 2
9464  * of the License, or (at your option) any later version.
9465  *
9466  * This program is distributed in the hope that it will be useful,
9467  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9468  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9469  * GNU General Public License for more details.
9470  */
9471 $$;
9472
9473 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9474     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9475       FROM  money.materialized_billable_xact_summary m
9476             LEFT JOIN action.circulation c ON (c.id = m.id)
9477             LEFT JOIN money.grocery g ON (g.id = m.id)
9478             LEFT JOIN booking.reservation r ON (r.id = m.id);
9479
9480 CREATE TABLE config.marc21_rec_type_map (
9481     code        TEXT    PRIMARY KEY,
9482     type_val    TEXT    NOT NULL,
9483     blvl_val    TEXT    NOT NULL
9484 );
9485
9486 CREATE TABLE config.marc21_ff_pos_map (
9487     id          SERIAL  PRIMARY KEY,
9488     fixed_field TEXT    NOT NULL,
9489     tag         TEXT    NOT NULL,
9490     rec_type    TEXT    NOT NULL,
9491     start_pos   INT     NOT NULL,
9492     length      INT     NOT NULL,
9493     default_val TEXT    NOT NULL DEFAULT ' '
9494 );
9495
9496 CREATE TABLE config.marc21_physical_characteristic_type_map (
9497     ptype_key   TEXT    PRIMARY KEY,
9498     label       TEXT    NOT NULL -- I18N
9499 );
9500
9501 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9502     id          SERIAL  PRIMARY KEY,
9503     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9504     subfield    TEXT    NOT NULL,
9505     start_pos   INT     NOT NULL,
9506     length      INT     NOT NULL,
9507     label       TEXT    NOT NULL -- I18N
9508 );
9509
9510 CREATE TABLE config.marc21_physical_characteristic_value_map (
9511     id              SERIAL  PRIMARY KEY,
9512     value           TEXT    NOT NULL,
9513     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9514     label           TEXT    NOT NULL -- I18N
9515 );
9516
9517 ----------------------------------
9518 -- MARC21 record structure data --
9519 ----------------------------------
9520
9521 -- Record type map
9522 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9523 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9524 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9525 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9526 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9527 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9528 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9529 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9530
9531 ------ Physical Characteristics
9532
9533 -- Map
9534 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9535 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9536 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9537 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9538 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9539 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9540 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9541 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');
9542 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9543 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9544 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9545 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9546 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9547 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');
9548 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9549 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9550 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9551 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9552 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9553 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9554 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9555 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9556 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9557 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9558 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');
9559 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');
9560 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');
9561 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');
9562 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9563 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');
9564 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9565 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9566 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9567 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9568 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9569 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9570 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
9571 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');
9572 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9573 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');
9574 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
9575 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9576 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9577 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9578 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9579 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9580 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9581 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');
9582
9583 -- Electronic Resource
9584 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9585 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9586 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');
9587 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');
9588 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');
9589 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');
9590 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');
9591 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');
9592 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');
9593 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');
9594 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9595 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9596 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9597 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
9598 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');
9599 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');
9600 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9601 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');
9602 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9603 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');
9604 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9605 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9606 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9607 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.');
9608 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.');
9609 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.');
9610 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.');
9611 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.');
9612 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');
9613 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.');
9614 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9615 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.');
9616 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9617 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
9618 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)');
9619 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
9620 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9621 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9622 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9623 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9624 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');
9625 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9626 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');
9627 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');
9628 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9629 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9630 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9631 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');
9632 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
9633 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9634 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
9635 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');
9636 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');
9637 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');
9638 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)');
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_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9643 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9644 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9645 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9646 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9647 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9648 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
9649 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9650 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');
9651 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9652 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9653 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9654
9655 -- Globe
9656 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9657 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9658 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');
9659 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');
9660 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');
9661 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');
9662 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9663 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9664 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
9665 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');
9666 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9667 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9668 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9669 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9670 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9671 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9672 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9673 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9674 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9675 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9676 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9677 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9678 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9679 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9680 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');
9681 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9682 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9683
9684 -- Tactile Material
9685 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9686 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9687 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9688 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9689 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9690 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');
9691 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9692 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9693 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9694 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');
9695 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');
9696 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');
9697 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');
9698 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');
9699 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');
9700 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9701 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9702 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9703 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9704 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9705 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9706 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9707 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');
9708 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9709 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9710 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9711 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');
9712 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');
9713 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');
9714 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9715 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');
9716 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');
9717 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');
9718 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');
9719 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');
9720 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');
9721 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9722 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');
9723 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');
9724 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9725 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9726 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
9727 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');
9728 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');
9729 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');
9730 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9731 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9732
9733 -- Projected Graphic
9734 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9735 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9736 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');
9737 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9738 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');
9739 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');
9740 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9741 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
9742 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9743 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9744 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');
9745 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9746 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');
9747 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9748 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');
9749 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9750 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9751 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9752 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9753 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9754 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');
9755 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');
9756 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9757 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9758 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9759 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9760 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','f','5','1','Sound on medium or separate');
9761 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');
9762 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');
9763 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9764 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9765 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');
9766 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');
9767 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');
9768 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');
9769 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');
9770 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');
9771 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');
9772 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9773 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9774 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9775 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9776 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9777 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.');
9778 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.');
9779 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.');
9780 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.');
9781 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.');
9782 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.');
9783 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.');
9784 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.)');
9785 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.)');
9786 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.)');
9787 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.)');
9788 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9789 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.)');
9790 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.)');
9791 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.)');
9792 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.)');
9793 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9794 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9795 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9796 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9797 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9798 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9799 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');
9800 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');
9801 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');
9802 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9803 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9804
9805 -- Microform
9806 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9807 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9808 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');
9809 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');
9810 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');
9811 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');
9812 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9813 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');
9814 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9815 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9816 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9817 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9818 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9819 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9820 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9821 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9822 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
9823 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.');
9824 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.');
9825 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.');
9826 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9827 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.');
9828 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.)');
9829 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.)');
9830 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.)');
9831 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.)');
9832 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9833 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9834 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');
9835 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)');
9836 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)');
9837 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)');
9838 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)');
9839 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-)');
9840 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9841 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Reduction ratio varies');
9842 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9843 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');
9844 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9845 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9846 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9847 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9848 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9849 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');
9850 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9851 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9853 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');
9854 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9855 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9856 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
9857 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');
9858 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');
9859 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');
9860 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed generation');
9861 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9862 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9863 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');
9864 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');
9865 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');
9866 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');
9867 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');
9868 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');
9869 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');
9870 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');
9871 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');
9872 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9873 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9874
9875 -- Non-projected Graphic
9876 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9877 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
9878 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
9879 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
9880 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
9881 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');
9882 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
9883 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
9884 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
9885 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
9886 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');
9887 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
9888 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');
9889 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9890 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9891 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
9892 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');
9893 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');
9894 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9895 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');
9896 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9897 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9898 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9899 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
9900 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9901 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');
9902 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');
9903 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9904 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9905 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9906 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9907 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9908 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');
9909 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9910 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9911 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9912 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9913 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9914 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9915 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9916 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9917 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
9918 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9919 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');
9920 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');
9921 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9922 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9923 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9924 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9925 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9926 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');
9927 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9928 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9929 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9930 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9931 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9932 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9933 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9934 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9935
9936 -- Motion Picture
9937 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
9938 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
9939 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');
9940 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');
9941 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');
9942 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9943 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9944 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
9945 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');
9946 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9947 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');
9948 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9949 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9950 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9951 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
9952 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');
9953 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)');
9954 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
9955 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)');
9956 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');
9957 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');
9958 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9959 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9960 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');
9961 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');
9962 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');
9963 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9964 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
9965 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');
9966 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');
9967 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');
9968 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');
9969 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');
9970 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');
9971 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');
9972 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9973 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9974 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9975 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9976 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
9977 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.');
9978 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.');
9979 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.');
9980 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.');
9981 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.');
9982 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.');
9983 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.');
9984 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9985 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9986 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
9987 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9988 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
9989 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');
9990 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');
9991 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
9992 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9993 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9994 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
9995 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');
9996 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
9997 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
9998 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
9999 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');
10000 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');
10001 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');
10002 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10003 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10004
10005 -- Remote-sensing Image
10006 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
10007 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
10008 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10009 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
10010 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
10011 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
10012 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
10013 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');
10014 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10015 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10016 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
10017 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');
10018 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');
10019 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
10020 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');
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_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
10023 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%');
10024 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%');
10025 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%');
10026 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%');
10027 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%');
10028 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%');
10029 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%');
10030 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%');
10031 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%');
10032 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%');
10033 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');
10034 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10035 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
10036 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
10037 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');
10038 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');
10039 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');
10040 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');
10041 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');
10042 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');
10043 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');
10044 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');
10045 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');
10046 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10047 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10048 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
10049 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
10050 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');
10051 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');
10052 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');
10053 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');
10054 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10055 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10056 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
10057 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
10058 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
10059 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10060 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10061 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
10062 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');
10063 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');
10064 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');
10065 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');
10066 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');
10067 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)');
10068 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');
10069 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
10070 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');
10071 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)');
10072 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)');
10073 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)');
10074 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');
10075 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');
10076 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');
10077 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');
10078 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');
10079 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');
10080 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');
10081 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');
10082 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');
10083 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');
10084 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');
10085 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');
10086 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');
10087 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');
10088 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');
10089 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');
10090 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');
10091 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');
10092 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');
10093 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');
10094 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');
10095 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)');
10096 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');
10097 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
10098 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
10099 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');
10100 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');
10101 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10102 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10103
10104 -- Sound Recording
10105 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
10106 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
10107 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');
10108 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
10109 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');
10110 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');
10111 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
10112 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');
10113 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');
10114 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10115 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');
10116 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10117 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
10118 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');
10119 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');
10120 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');
10121 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');
10122 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');
10123 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');
10124 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');
10125 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');
10126 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');
10127 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');
10128 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');
10129 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');
10130 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');
10131 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');
10132 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10133 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10134 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
10135 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10136 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
10137 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10138 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10139 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10140 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10141 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');
10142 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');
10143 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');
10144 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10145 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10146 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10147 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.');
10148 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.');
10149 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.');
10150 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.');
10151 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.');
10152 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.');
10153 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.)');
10154 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.');
10155 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');
10156 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.');
10157 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.');
10158 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10159 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10160 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
10161 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('l',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1/8 in.');
10162 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.');
10163 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10164 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1/2 in.');
10165 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.');
10166 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10167 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10168 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
10169 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');
10170 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');
10171 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');
10172 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');
10173 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');
10174 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');
10175 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');
10176 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10177 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10178 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10179 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');
10180 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');
10181 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');
10182 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');
10183 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');
10184 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');
10185 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');
10186 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');
10187 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');
10188 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10189 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10190 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
10191 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');
10192 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');
10193 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');
10194 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');
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
10198 -- Videorecording
10199 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10200 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10201 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10202 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10203 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10204 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10205 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10206 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10207 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10208 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');
10209 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10210 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10211 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');
10212 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10213 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10214 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10215 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10216 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10217 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');
10218 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10219 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');
10220 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10221 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10222 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10223 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10224 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');
10225 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');
10226 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');
10227 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');
10228 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.');
10229 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.');
10230 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10231 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10232 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10233 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');
10234 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');
10235 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');
10236 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10237 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10238 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');
10239 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');
10240 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');
10241 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');
10242 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');
10243 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');
10244 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');
10245 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10246 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10247 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10248 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10249 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10250 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.');
10251 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.');
10252 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.');
10253 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.');
10254 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.');
10255 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.');
10256 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10257 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10258 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10259 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10260 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10261 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');
10262 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');
10263 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10264 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10265 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10266
10267 -- Fixed Field position data -- 0-based!
10268 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10269 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10270 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10271 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10272 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10273 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10274 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10275 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10276 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10277 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10278 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10279 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10280 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10281 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10282 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10283 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10284 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10285 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10286 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10287 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10288 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10289 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10290 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10291 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10292 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10293 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10294 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10295 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10296 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10297 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10298 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10299 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10300 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10301 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10302 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10303 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10304 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10305 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10306 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10307 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10308 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10309 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10310 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10311 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10312 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10313 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10314 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10315 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10316 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10317 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10318 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10319 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10320 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10321 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10322 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10323 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10324 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10325 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10326 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10327 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10328 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10329 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10330 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10331 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10332 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10333 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10334 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10335 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10336 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10337 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10338 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10339 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10340 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10341 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10342 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10343 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10344 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10345 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10346 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10347 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10348 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10349 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10350 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10351 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10352 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10353 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10354 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10355 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10356 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10357 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10358 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10359 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10360 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10361 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10362 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10363 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10364 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10365 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10366 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10367 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10368 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10369 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10370 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10371 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10372 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10373 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10374 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10375 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10376 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10377 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10378 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10379 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10380 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10381 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10382 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10383 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10384 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10385 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10386 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10387 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10388 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10389 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10390 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10391 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10392 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10393 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10394 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10395 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10396 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10397 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10398 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10399 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10400 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10401 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10402 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');
10403 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');
10404 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10405 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10406 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10407 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10408 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10409 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10410 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10411 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10412 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10413 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10414
10415 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10416 DECLARE
10417         ldr         RECORD;
10418         tval        TEXT;
10419         tval_rec    RECORD;
10420         bval        TEXT;
10421         bval_rec    RECORD;
10422     retval      config.marc21_rec_type_map%ROWTYPE;
10423 BEGIN
10424     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10425
10426     IF ldr.id IS NULL THEN
10427         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10428         RETURN retval;
10429     END IF;
10430
10431     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10432     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10433
10434
10435     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10436     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10437
10438     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10439
10440     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10441
10442
10443     IF retval.code IS NULL THEN
10444         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10445     END IF;
10446
10447     RETURN retval;
10448 END;
10449 $func$ LANGUAGE PLPGSQL;
10450
10451 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10452 DECLARE
10453     rtype       TEXT;
10454     ff_pos      RECORD;
10455     tag_data    RECORD;
10456     val         TEXT;
10457 BEGIN
10458     rtype := (biblio.marc21_record_type( rid )).code;
10459     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10460         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10461             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10462             RETURN val;
10463         END LOOP;
10464         val := REPEAT( ff_pos.default_val, ff_pos.length );
10465         RETURN val;
10466     END LOOP;
10467
10468     RETURN NULL;
10469 END;
10470 $func$ LANGUAGE PLPGSQL;
10471
10472 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10473 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10474 DECLARE
10475     rowid   INT := 0;
10476     _007    RECORD;
10477     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10478     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10479     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10480     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10481 BEGIN
10482
10483     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10484
10485     IF _007.id IS NOT NULL THEN
10486         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10487
10488         IF ptype.ptype_key IS NOT NULL THEN
10489             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10490                 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 );
10491
10492                 IF pval.id IS NOT NULL THEN
10493                     rowid := rowid + 1;
10494                     retval.id := rowid;
10495                     retval.record := rid;
10496                     retval.ptype := ptype.ptype_key;
10497                     retval.subfield := psf.id;
10498                     retval.value := pval.id;
10499                     RETURN NEXT retval;
10500                 END IF;
10501
10502             END LOOP;
10503         END IF;
10504     END IF;
10505
10506     RETURN;
10507 END;
10508 $func$ LANGUAGE PLPGSQL;
10509
10510 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10511 DROP VIEW IF EXISTS money.open_usr_summary;
10512 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10513
10514 -- The view should supply defaults for numeric (amount) columns
10515 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10516     SELECT  xact.id,
10517         xact.usr,
10518         xact.xact_start,
10519         xact.xact_finish,
10520         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10521         credit.payment_ts AS last_payment_ts,
10522         credit.note AS last_payment_note,
10523         credit.payment_type AS last_payment_type,
10524         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10525         debit.billing_ts AS last_billing_ts,
10526         debit.note AS last_billing_note,
10527         debit.billing_type AS last_billing_type,
10528         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10529         p.relname AS xact_type
10530       FROM  money.billable_xact xact
10531         JOIN pg_class p ON xact.tableoid = p.oid
10532         LEFT JOIN (
10533             SELECT  billing.xact,
10534                 sum(billing.amount) AS amount,
10535                 max(billing.billing_ts) AS billing_ts,
10536                 last(billing.note) AS note,
10537                 last(billing.billing_type) AS billing_type
10538               FROM  money.billing
10539               WHERE billing.voided IS FALSE
10540               GROUP BY billing.xact
10541             ) debit ON xact.id = debit.xact
10542         LEFT JOIN (
10543             SELECT  payment_view.xact,
10544                 sum(payment_view.amount) AS amount,
10545                 max(payment_view.payment_ts) AS payment_ts,
10546                 last(payment_view.note) AS note,
10547                 last(payment_view.payment_type) AS payment_type
10548               FROM  money.payment_view
10549               WHERE payment_view.voided IS FALSE
10550               GROUP BY payment_view.xact
10551             ) credit ON xact.id = credit.xact
10552       ORDER BY debit.billing_ts, credit.payment_ts;
10553
10554 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10555     SELECT * FROM money.billable_xact_summary_location_view
10556     WHERE xact_finish IS NULL;
10557
10558 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10559     SELECT 
10560         usr,
10561         SUM(total_paid) AS total_paid,
10562         SUM(total_owed) AS total_owed,
10563         SUM(balance_owed) AS balance_owed
10564     FROM  money.materialized_billable_xact_summary
10565     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10566     GROUP BY usr;
10567
10568 CREATE OR REPLACE VIEW money.usr_summary AS
10569     SELECT 
10570         usr, 
10571         sum(total_paid) AS total_paid, 
10572         sum(total_owed) AS total_owed, 
10573         sum(balance_owed) AS balance_owed
10574     FROM money.materialized_billable_xact_summary
10575     GROUP BY usr;
10576
10577 CREATE OR REPLACE VIEW money.open_usr_summary AS
10578     SELECT 
10579         usr, 
10580         sum(total_paid) AS total_paid, 
10581         sum(total_owed) AS total_owed, 
10582         sum(balance_owed) AS balance_owed
10583     FROM money.materialized_billable_xact_summary
10584     WHERE xact_finish IS NULL
10585     GROUP BY usr;
10586
10587 -- 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;
10588
10589 CREATE TABLE config.biblio_fingerprint (
10590         id                      SERIAL  PRIMARY KEY,
10591         name            TEXT    NOT NULL, 
10592         xpath           TEXT    NOT NULL,
10593     first_word  BOOL    NOT NULL DEFAULT FALSE,
10594         format          TEXT    NOT NULL DEFAULT 'marcxml'
10595 );
10596
10597 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10598     VALUES (
10599         'Title',
10600         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10601             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10602             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10603             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10604             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10605         'marcxml'
10606     );
10607
10608 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10609     VALUES (
10610         'Author',
10611         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10612             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10613             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10614             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10615             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10616         'marcxml',
10617         TRUE
10618     );
10619
10620 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10621 DECLARE
10622     qual        INT;
10623     ldr         TEXT;
10624     tval        TEXT;
10625     tval_rec    RECORD;
10626     bval        TEXT;
10627     bval_rec    RECORD;
10628     type_map    RECORD;
10629     ff_pos      RECORD;
10630     ff_tag_data TEXT;
10631 BEGIN
10632
10633     IF marc IS NULL OR marc = '' THEN
10634         RETURN NULL;
10635     END IF;
10636
10637     -- First, the count of tags
10638     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10639
10640     -- now go through a bunch of pain to get the record type
10641     IF best_type IS NOT NULL THEN
10642         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10643
10644         IF ldr IS NOT NULL THEN
10645             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10646             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10647
10648
10649             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10650             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10651
10652             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10653
10654             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10655
10656             IF type_map.code IS NOT NULL THEN
10657                 IF best_type = type_map.code THEN
10658                     qual := qual + qual / 2;
10659                 END IF;
10660
10661                 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
10662                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10663                     IF ff_tag_data = best_lang THEN
10664                             qual := qual + 100;
10665                     END IF;
10666                 END LOOP;
10667             END IF;
10668         END IF;
10669     END IF;
10670
10671     -- Now look for some quality metrics
10672     -- DCL record?
10673     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10674         qual := qual + 10;
10675     END IF;
10676
10677     -- From OCLC?
10678     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10679         qual := qual + 10;
10680     END IF;
10681
10682     RETURN qual;
10683
10684 END;
10685 $func$ LANGUAGE PLPGSQL;
10686
10687 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10688 DECLARE
10689     idx     config.biblio_fingerprint%ROWTYPE;
10690     xfrm        config.xml_transform%ROWTYPE;
10691     prev_xfrm   TEXT;
10692     transformed_xml TEXT;
10693     xml_node    TEXT;
10694     xml_node_list   TEXT[];
10695     raw_text    TEXT;
10696     output_text TEXT := '';
10697 BEGIN
10698
10699     IF marc IS NULL OR marc = '' THEN
10700         RETURN NULL;
10701     END IF;
10702
10703     -- Loop over the indexing entries
10704     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10705
10706         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10707
10708         -- See if we can skip the XSLT ... it's expensive
10709         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10710             -- Can't skip the transform
10711             IF xfrm.xslt <> '---' THEN
10712                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10713             ELSE
10714                 transformed_xml := marc;
10715             END IF;
10716
10717             prev_xfrm := xfrm.name;
10718         END IF;
10719
10720         raw_text := COALESCE(
10721             naco_normalize(
10722                 ARRAY_TO_STRING(
10723                     oils_xpath(
10724                         '//text()',
10725                         (oils_xpath(
10726                             idx.xpath,
10727                             transformed_xml,
10728                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10729                         ))[1]
10730                     ),
10731                     ''
10732                 )
10733             ),
10734             ''
10735         );
10736
10737         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10738         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10739
10740         IF idx.first_word IS TRUE THEN
10741             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10742         END IF;
10743
10744         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10745
10746     END LOOP;
10747
10748     RETURN output_text;
10749
10750 END;
10751 $func$ LANGUAGE PLPGSQL;
10752
10753 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10754 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10755 BEGIN
10756
10757     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10758
10759     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10760         RETURN NEW;
10761     END IF;
10762
10763     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10764     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10765
10766     RETURN NEW;
10767
10768 END;
10769 $func$ LANGUAGE PLPGSQL;
10770
10771 CREATE TABLE config.internal_flag (
10772     name    TEXT    PRIMARY KEY,
10773     value   TEXT,
10774     enabled BOOL    NOT NULL DEFAULT FALSE
10775 );
10776 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10777 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10778 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10779 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10780 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10781 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10782 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10783 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10784 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10785 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10786
10787 CREATE TABLE authority.bib_linking (
10788     id          BIGSERIAL   PRIMARY KEY,
10789     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10790     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10791 );
10792 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10793 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10794
10795 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10796     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10797 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10798
10799 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10800     DELETE FROM authority.bib_linking WHERE bib = $1;
10801     INSERT INTO authority.bib_linking (bib, authority)
10802         SELECT  y.bib,
10803                 y.authority
10804           FROM (    SELECT  DISTINCT $1 AS bib,
10805                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10806                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10807                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10808                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10809     SELECT $1;
10810 $func$ LANGUAGE SQL;
10811
10812 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10813 BEGIN
10814     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10815     IF NOT FOUND THEN
10816         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10817     END IF;
10818     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)
10819         SELECT  bib_id,
10820                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10821                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10822                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10823                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10824                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10825                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10826                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10827                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10828                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10829                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10830                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10831                 (   SELECT  v.value
10832                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10833                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10834                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10835                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10836                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
10837                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
10838
10839     RETURN;
10840 END;
10841 $func$ LANGUAGE PLPGSQL;
10842
10843 CREATE TABLE config.metabib_class (
10844     name    TEXT    PRIMARY KEY,
10845     label   TEXT    NOT NULL UNIQUE
10846 );
10847
10848 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10849 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10850 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10851 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10852 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10853
10854 CREATE TABLE metabib.facet_entry (
10855         id              BIGSERIAL       PRIMARY KEY,
10856         source          BIGINT          NOT NULL,
10857         field           INT             NOT NULL,
10858         value           TEXT            NOT NULL
10859 );
10860
10861 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10862 DECLARE
10863     fclass          RECORD;
10864     ind_data        metabib.field_entry_template%ROWTYPE;
10865 BEGIN
10866     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10867     IF NOT FOUND THEN
10868         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10869             -- RAISE NOTICE 'Emptying out %', fclass.name;
10870             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10871         END LOOP;
10872         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10873     END IF;
10874
10875     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10876         IF ind_data.field < 0 THEN
10877             ind_data.field = -1 * ind_data.field;
10878             INSERT INTO metabib.facet_entry (field, source, value)
10879                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10880         ELSE
10881             EXECUTE $$
10882                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10883                     VALUES ($$ ||
10884                         quote_literal(ind_data.field) || $$, $$ ||
10885                         quote_literal(ind_data.source) || $$, $$ ||
10886                         quote_literal(ind_data.value) ||
10887                     $$);$$;
10888         END IF;
10889
10890     END LOOP;
10891
10892     RETURN;
10893 END;
10894 $func$ LANGUAGE PLPGSQL;
10895
10896 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
10897 DECLARE
10898     uris            TEXT[];
10899     uri_xml         TEXT;
10900     uri_label       TEXT;
10901     uri_href        TEXT;
10902     uri_use         TEXT;
10903     uri_owner       TEXT;
10904     uri_owner_id    INT;
10905     uri_id          INT;
10906     uri_cn_id       INT;
10907     uri_map_id      INT;
10908 BEGIN
10909
10910     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
10911     IF ARRAY_UPPER(uris,1) > 0 THEN
10912         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
10913             -- First we pull info out of the 856
10914             uri_xml     := uris[i];
10915
10916             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
10917             CONTINUE WHEN uri_href IS NULL;
10918
10919             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10920             CONTINUE WHEN uri_label IS NULL;
10921
10922             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
10923             CONTINUE WHEN uri_owner IS NULL;
10924
10925             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10926
10927             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
10928
10929             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
10930             CONTINUE WHEN NOT FOUND;
10931
10932             -- now we look for a matching uri
10933             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10934             IF NOT FOUND THEN -- create one
10935                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
10936                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10937             END IF;
10938
10939             -- we need a call number to link through
10940             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;
10941             IF NOT FOUND THEN
10942                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
10943                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
10944                 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;
10945             END IF;
10946
10947             -- now, link them if they're not already
10948             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
10949             IF NOT FOUND THEN
10950                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
10951             END IF;
10952
10953         END LOOP;
10954     END IF;
10955
10956     RETURN;
10957 END;
10958 $func$ LANGUAGE PLPGSQL;
10959
10960 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
10961 DECLARE
10962     source_count    INT;
10963     old_mr          BIGINT;
10964     tmp_mr          metabib.metarecord%ROWTYPE;
10965     deleted_mrs     BIGINT[];
10966 BEGIN
10967
10968     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
10969
10970     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
10971
10972         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
10973             old_mr := tmp_mr.id;
10974         ELSE
10975             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
10976             IF source_count = 0 THEN -- No other records
10977                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
10978                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
10979             END IF;
10980         END IF;
10981
10982     END LOOP;
10983
10984     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
10985         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
10986         IF old_mr IS NULL THEN -- nope, create one and grab its id
10987             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
10988             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
10989         ELSE -- indeed there is. update it with a null cache and recalcualated master record
10990             UPDATE  metabib.metarecord
10991               SET   mods = NULL,
10992                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
10993               WHERE id = old_mr;
10994         END IF;
10995     ELSE -- there was one we already attached to, update its mods cache and master_record
10996         UPDATE  metabib.metarecord
10997           SET   mods = NULL,
10998                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
10999           WHERE id = old_mr;
11000     END IF;
11001
11002     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
11003
11004     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
11005         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
11006     END IF;
11007
11008     RETURN old_mr;
11009
11010 END;
11011 $func$ LANGUAGE PLPGSQL;
11012
11013 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
11014 BEGIN
11015     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
11016     IF NOT FOUND THEN
11017         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
11018     END IF;
11019     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
11020         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
11021
11022     RETURN;
11023 END;
11024 $func$ LANGUAGE PLPGSQL;
11025
11026 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
11027 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
11028 BEGIN
11029
11030     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
11031         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
11032         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
11033         RETURN NEW; -- and we're done
11034     END IF;
11035
11036     IF TG_OP = 'UPDATE' THEN -- re-ingest?
11037         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
11038
11039         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
11040             RETURN NEW;
11041         END IF;
11042     END IF;
11043
11044     -- Record authority linking
11045     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
11046     IF NOT FOUND THEN
11047         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11048     END IF;
11049
11050     -- Flatten and insert the mfr data
11051     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11052     IF NOT FOUND THEN
11053         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11054         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11055         IF NOT FOUND THEN
11056             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
11057         END IF;
11058     END IF;
11059
11060     -- Gather and insert the field entry data
11061     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11062
11063     -- Located URI magic
11064     IF TG_OP = 'INSERT' THEN
11065         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11066         IF NOT FOUND THEN
11067             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11068         END IF;
11069     ELSE
11070         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11071         IF NOT FOUND THEN
11072             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11073         END IF;
11074     END IF;
11075
11076     -- (re)map metarecord-bib linking
11077     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11078         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11079         IF NOT FOUND THEN
11080             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11081         END IF;
11082     ELSE -- we're doing an update, and we're not deleted, remap
11083         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11084         IF NOT FOUND THEN
11085             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11086         END IF;
11087     END IF;
11088
11089     RETURN NEW;
11090 END;
11091 $func$ LANGUAGE PLPGSQL;
11092
11093 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
11094 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 ();
11095
11096 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
11097
11098 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
11099 DECLARE
11100     xpath_list  TEXT[];
11101     select_list TEXT[];
11102     where_list  TEXT[];
11103     q           TEXT;
11104     out_record  RECORD;
11105     empty_test  RECORD;
11106 BEGIN
11107     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
11108  
11109     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
11110  
11111     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
11112         IF xpath_list[i] = 'null()' THEN
11113             select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
11114         ELSE
11115             select_list := ARRAY_APPEND(
11116                 select_list,
11117                 $sel$
11118                 EXPLODE_ARRAY(
11119                     COALESCE(
11120                         NULLIF(
11121                             oils_xpath(
11122                                 $sel$ ||
11123                                     quote_literal(
11124                                         CASE
11125                                             WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
11126                                             ELSE xpath_list[i] || '//text()'
11127                                         END
11128                                     ) ||
11129                                 $sel$,
11130                                 $sel$ || document_field || $sel$
11131                             ),
11132                            '{}'::TEXT[]
11133                         ),
11134                         '{NULL}'::TEXT[]
11135                     )
11136                 ) AS c_$sel$ || i
11137             );
11138             where_list := ARRAY_APPEND(
11139                 where_list,
11140                 'c_' || i || ' IS NOT NULL'
11141             );
11142         END IF;
11143     END LOOP;
11144  
11145     q := $q$
11146 SELECT * FROM (
11147     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11148 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
11149     -- RAISE NOTICE 'query: %', q;
11150  
11151     FOR out_record IN EXECUTE q LOOP
11152         RETURN NEXT out_record;
11153     END LOOP;
11154  
11155     RETURN;
11156 END;
11157 $func$ LANGUAGE PLPGSQL IMMUTABLE;
11158
11159 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11160 DECLARE
11161
11162     owning_lib      TEXT;
11163     circ_lib        TEXT;
11164     call_number     TEXT;
11165     copy_number     TEXT;
11166     status          TEXT;
11167     location        TEXT;
11168     circulate       TEXT;
11169     deposit         TEXT;
11170     deposit_amount  TEXT;
11171     ref             TEXT;
11172     holdable        TEXT;
11173     price           TEXT;
11174     barcode         TEXT;
11175     circ_modifier   TEXT;
11176     circ_as_type    TEXT;
11177     alert_message   TEXT;
11178     opac_visible    TEXT;
11179     pub_note        TEXT;
11180     priv_note       TEXT;
11181
11182     attr_def        RECORD;
11183     tmp_attr_set    RECORD;
11184     attr_set        vandelay.import_item%ROWTYPE;
11185
11186     xpath           TEXT;
11187
11188 BEGIN
11189
11190     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11191
11192     IF FOUND THEN
11193
11194         attr_set.definition := attr_def.id; 
11195     
11196         -- Build the combined XPath
11197     
11198         owning_lib :=
11199             CASE
11200                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11201                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11202                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11203             END;
11204     
11205         circ_lib :=
11206             CASE
11207                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11208                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11209                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11210             END;
11211     
11212         call_number :=
11213             CASE
11214                 WHEN attr_def.call_number IS NULL THEN 'null()'
11215                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11216                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11217             END;
11218     
11219         copy_number :=
11220             CASE
11221                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11222                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11223                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11224             END;
11225     
11226         status :=
11227             CASE
11228                 WHEN attr_def.status IS NULL THEN 'null()'
11229                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11230                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11231             END;
11232     
11233         location :=
11234             CASE
11235                 WHEN attr_def.location IS NULL THEN 'null()'
11236                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11237                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11238             END;
11239     
11240         circulate :=
11241             CASE
11242                 WHEN attr_def.circulate IS NULL THEN 'null()'
11243                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11244                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11245             END;
11246     
11247         deposit :=
11248             CASE
11249                 WHEN attr_def.deposit IS NULL THEN 'null()'
11250                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11251                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11252             END;
11253     
11254         deposit_amount :=
11255             CASE
11256                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11257                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11258                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11259             END;
11260     
11261         ref :=
11262             CASE
11263                 WHEN attr_def.ref IS NULL THEN 'null()'
11264                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11265                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11266             END;
11267     
11268         holdable :=
11269             CASE
11270                 WHEN attr_def.holdable IS NULL THEN 'null()'
11271                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11272                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11273             END;
11274     
11275         price :=
11276             CASE
11277                 WHEN attr_def.price IS NULL THEN 'null()'
11278                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11279                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11280             END;
11281     
11282         barcode :=
11283             CASE
11284                 WHEN attr_def.barcode IS NULL THEN 'null()'
11285                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11286                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11287             END;
11288     
11289         circ_modifier :=
11290             CASE
11291                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11292                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11293                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11294             END;
11295     
11296         circ_as_type :=
11297             CASE
11298                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11299                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11300                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11301             END;
11302     
11303         alert_message :=
11304             CASE
11305                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11306                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11307                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11308             END;
11309     
11310         opac_visible :=
11311             CASE
11312                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11313                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11314                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11315             END;
11316
11317         pub_note :=
11318             CASE
11319                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11320                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11321                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11322             END;
11323         priv_note :=
11324             CASE
11325                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11326                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11327                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11328             END;
11329     
11330     
11331         xpath := 
11332             owning_lib      || '|' || 
11333             circ_lib        || '|' || 
11334             call_number     || '|' || 
11335             copy_number     || '|' || 
11336             status          || '|' || 
11337             location        || '|' || 
11338             circulate       || '|' || 
11339             deposit         || '|' || 
11340             deposit_amount  || '|' || 
11341             ref             || '|' || 
11342             holdable        || '|' || 
11343             price           || '|' || 
11344             barcode         || '|' || 
11345             circ_modifier   || '|' || 
11346             circ_as_type    || '|' || 
11347             alert_message   || '|' || 
11348             pub_note        || '|' || 
11349             priv_note       || '|' || 
11350             opac_visible;
11351
11352         -- RAISE NOTICE 'XPath: %', xpath;
11353         
11354         FOR tmp_attr_set IN
11355                 SELECT  *
11356                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11357                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11358                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11359                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11360         LOOP
11361     
11362             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11363             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11364
11365             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11366             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11367     
11368             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11369             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11370             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11371     
11372             SELECT  id INTO attr_set.location
11373               FROM  asset.copy_location
11374               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11375                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11376     
11377             attr_set.circulate      :=
11378                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11379                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11380
11381             attr_set.deposit        :=
11382                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11383                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11384
11385             attr_set.holdable       :=
11386                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11387                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11388
11389             attr_set.opac_visible   :=
11390                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11391                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11392
11393             attr_set.ref            :=
11394                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11395                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11396     
11397             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11398             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11399             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11400     
11401             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11402             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11403             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11404             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11405             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11406             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11407             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11408             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11409     
11410             RETURN NEXT attr_set;
11411     
11412         END LOOP;
11413     
11414     END IF;
11415
11416     RETURN;
11417
11418 END;
11419 $$ LANGUAGE PLPGSQL;
11420
11421 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11422 DECLARE
11423     attr_def    BIGINT;
11424     item_data   vandelay.import_item%ROWTYPE;
11425 BEGIN
11426
11427     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11428
11429     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11430         INSERT INTO vandelay.import_item (
11431             record,
11432             definition,
11433             owning_lib,
11434             circ_lib,
11435             call_number,
11436             copy_number,
11437             status,
11438             location,
11439             circulate,
11440             deposit,
11441             deposit_amount,
11442             ref,
11443             holdable,
11444             price,
11445             barcode,
11446             circ_modifier,
11447             circ_as_type,
11448             alert_message,
11449             pub_note,
11450             priv_note,
11451             opac_visible
11452         ) VALUES (
11453             NEW.id,
11454             item_data.definition,
11455             item_data.owning_lib,
11456             item_data.circ_lib,
11457             item_data.call_number,
11458             item_data.copy_number,
11459             item_data.status,
11460             item_data.location,
11461             item_data.circulate,
11462             item_data.deposit,
11463             item_data.deposit_amount,
11464             item_data.ref,
11465             item_data.holdable,
11466             item_data.price,
11467             item_data.barcode,
11468             item_data.circ_modifier,
11469             item_data.circ_as_type,
11470             item_data.alert_message,
11471             item_data.pub_note,
11472             item_data.priv_note,
11473             item_data.opac_visible
11474         );
11475     END LOOP;
11476
11477     RETURN NULL;
11478 END;
11479 $func$ LANGUAGE PLPGSQL;
11480
11481 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11482 BEGIN
11483     EXECUTE $$
11484         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11485     $$;
11486         RETURN TRUE;
11487 END;
11488 $creator$ LANGUAGE 'plpgsql';
11489
11490 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11491 BEGIN
11492     EXECUTE $$
11493         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11494             audit_id    BIGINT                          PRIMARY KEY,
11495             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11496             audit_action        TEXT                            NOT NULL,
11497             LIKE $$ || sch || $$.$$ || tbl || $$
11498         );
11499     $$;
11500         RETURN TRUE;
11501 END;
11502 $creator$ LANGUAGE 'plpgsql';
11503
11504 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11505 BEGIN
11506     EXECUTE $$
11507         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11508         RETURNS TRIGGER AS $func$
11509         BEGIN
11510             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11511                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11512                     now(),
11513                     SUBSTR(TG_OP,1,1),
11514                     OLD.*;
11515             RETURN NULL;
11516         END;
11517         $func$ LANGUAGE 'plpgsql';
11518     $$;
11519         RETURN TRUE;
11520 END;
11521 $creator$ LANGUAGE 'plpgsql';
11522
11523 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11524 BEGIN
11525     EXECUTE $$
11526         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11527             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11528             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11529     $$;
11530         RETURN TRUE;
11531 END;
11532 $creator$ LANGUAGE 'plpgsql';
11533
11534 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11535 BEGIN
11536     EXECUTE $$
11537         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11538             SELECT      -1, now() as audit_time, '-' as audit_action, *
11539               FROM      $$ || sch || $$.$$ || tbl || $$
11540                 UNION ALL
11541             SELECT      *
11542               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11543     $$;
11544         RETURN TRUE;
11545 END;
11546 $creator$ LANGUAGE 'plpgsql';
11547
11548 -- The main event
11549
11550 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11551 BEGIN
11552     PERFORM acq.create_acq_seq(sch, tbl);
11553     PERFORM acq.create_acq_history(sch, tbl);
11554     PERFORM acq.create_acq_func(sch, tbl);
11555     PERFORM acq.create_acq_update_trigger(sch, tbl);
11556     PERFORM acq.create_acq_lifecycle(sch, tbl);
11557     RETURN TRUE;
11558 END;
11559 $creator$ LANGUAGE 'plpgsql';
11560
11561 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11562
11563 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11564     SELECT  fund.id AS fund,
11565             fund_debit.encumbrance AS encumbrance,
11566             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11567       FROM acq.fund AS fund
11568                         LEFT JOIN acq.fund_debit AS fund_debit
11569                                 ON ( fund.id = fund_debit.fund )
11570       GROUP BY 1,2;
11571
11572 CREATE TABLE acq.debit_attribution (
11573         id                     INT         NOT NULL PRIMARY KEY,
11574         fund_debit             INT         NOT NULL
11575                                            REFERENCES acq.fund_debit
11576                                            DEFERRABLE INITIALLY DEFERRED,
11577     debit_amount           NUMERIC     NOT NULL,
11578         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11579                                            DEFERRABLE INITIALLY DEFERRED,
11580     credit_amount          NUMERIC
11581 );
11582
11583 CREATE INDEX acq_attribution_debit_idx
11584         ON acq.debit_attribution( fund_debit );
11585
11586 CREATE INDEX acq_attribution_credit_idx
11587         ON acq.debit_attribution( funding_source_credit );
11588
11589 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11590 /*
11591 Function to attribute expenditures and encumbrances to funding source credits,
11592 and thereby to funding sources.
11593
11594 Read the debits in chonological order, attributing each one to one or
11595 more funding source credits.  Constraints:
11596
11597 1. Don't attribute more to a credit than the amount of the credit.
11598
11599 2. For a given fund, don't attribute more to a funding source than the
11600 source has allocated to that fund.
11601
11602 3. Attribute debits to credits with deadlines before attributing them to
11603 credits without deadlines.  Otherwise attribute to the earliest credits
11604 first, based on the deadline date when present, or on the effective date
11605 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11606 This ordering is defined by an ORDER BY clause on the view
11607 acq.ordered_funding_source_credit.
11608
11609 Start by truncating the table acq.debit_attribution.  Then insert a row
11610 into that table for each attribution.  If a debit cannot be fully
11611 attributed, insert a row for the unattributable balance, with the 
11612 funding_source_credit and credit_amount columns NULL.
11613 */
11614 DECLARE
11615         curr_fund_source_bal RECORD;
11616         seqno                INT;     -- sequence num for credits applicable to a fund
11617         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11618         fc                   RECORD;  -- used for loading t_fund_credit table
11619         sc                   RECORD;  -- used for loading t_fund_credit table
11620         --
11621         -- Used exclusively in the main loop:
11622         --
11623         deb                 RECORD;   -- current row from acq.fund_debit table
11624         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11625         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11626         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11627         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11628         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11629         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11630         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11631         attrib_count        INT;      -- populates id of acq.debit_attribution
11632 BEGIN
11633         --
11634         -- Load a temporary table.  For each combination of fund and funding source,
11635         -- load an entry with the total amount allocated to that fund by that source.
11636         -- This sum may reflect transfers as well as original allocations.  We will
11637         -- reduce this balance whenever we attribute debits to it.
11638         --
11639         CREATE TEMP TABLE t_fund_source_bal
11640         ON COMMIT DROP AS
11641                 SELECT
11642                         fund AS fund,
11643                         funding_source AS source,
11644                         sum( amount ) AS balance
11645                 FROM
11646                         acq.fund_allocation
11647                 GROUP BY
11648                         fund,
11649                         funding_source
11650                 HAVING
11651                         sum( amount ) > 0;
11652         --
11653         CREATE INDEX t_fund_source_bal_idx
11654                 ON t_fund_source_bal( fund, source );
11655         -------------------------------------------------------------------------------
11656         --
11657         -- Load another temporary table.  For each fund, load zero or more
11658         -- funding source credits from which that fund can get money.
11659         --
11660         CREATE TEMP TABLE t_fund_credit (
11661                 fund        INT,
11662                 seq         INT,
11663                 credit      INT
11664         ) ON COMMIT DROP;
11665         --
11666         FOR fc IN
11667                 SELECT DISTINCT fund
11668                 FROM acq.fund_allocation
11669                 ORDER BY fund
11670         LOOP                  -- Loop over the funds
11671                 seqno := 1;
11672                 FOR sc IN
11673                         SELECT
11674                                 ofsc.id
11675                         FROM
11676                                 acq.ordered_funding_source_credit AS ofsc
11677                         WHERE
11678                                 ofsc.funding_source IN
11679                                 (
11680                                         SELECT funding_source
11681                                         FROM acq.fund_allocation
11682                                         WHERE fund = fc.fund
11683                                 )
11684                 ORDER BY
11685                     ofsc.sort_priority,
11686                     ofsc.sort_date,
11687                     ofsc.id
11688                 LOOP                        -- Add each credit to the list
11689                         INSERT INTO t_fund_credit (
11690                                 fund,
11691                                 seq,
11692                                 credit
11693                         ) VALUES (
11694                                 fc.fund,
11695                                 seqno,
11696                                 sc.id
11697                         );
11698                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11699                         seqno := seqno + 1;
11700                 END LOOP;     -- Loop over credits for a given fund
11701         END LOOP;         -- Loop over funds
11702         --
11703         CREATE INDEX t_fund_credit_idx
11704                 ON t_fund_credit( fund, seq );
11705         -------------------------------------------------------------------------------
11706         --
11707         -- Load yet another temporary table.  This one is a list of funding source
11708         -- credits, with their balances.  We shall reduce those balances as we
11709         -- attribute debits to them.
11710         --
11711         CREATE TEMP TABLE t_credit
11712         ON COMMIT DROP AS
11713         SELECT
11714             fsc.id AS credit,
11715             fsc.funding_source AS source,
11716             fsc.amount AS balance,
11717             fs.currency_type AS currency_type
11718         FROM
11719             acq.funding_source_credit AS fsc,
11720             acq.funding_source fs
11721         WHERE
11722             fsc.funding_source = fs.id
11723                         AND fsc.amount > 0;
11724         --
11725         CREATE INDEX t_credit_idx
11726                 ON t_credit( credit );
11727         --
11728         -------------------------------------------------------------------------------
11729         --
11730         -- Now that we have loaded the lookup tables: loop through the debits,
11731         -- attributing each one to one or more funding source credits.
11732         -- 
11733         truncate table acq.debit_attribution;
11734         --
11735         attrib_count := 0;
11736         FOR deb in
11737                 SELECT
11738                         fd.id,
11739                         fd.fund,
11740                         fd.amount,
11741                         f.currency_type,
11742                         fd.encumbrance
11743                 FROM
11744                         acq.fund_debit fd,
11745                         acq.fund f
11746                 WHERE
11747                         fd.fund = f.id
11748                 ORDER BY
11749                         fd.id
11750         LOOP
11751                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11752                 --
11753                 debit_balance := deb.amount;
11754                 --
11755                 -- Loop over the funding source credits that are eligible
11756                 -- to pay for this debit
11757                 --
11758                 FOR fund_credit IN
11759                         SELECT
11760                                 credit
11761                         FROM
11762                                 t_fund_credit
11763                         WHERE
11764                                 fund = deb.fund
11765                         ORDER BY
11766                                 seq
11767                 LOOP
11768                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11769                         --
11770                         -- Look up the balance for this credit.  If it's zero, then
11771                         -- it's not useful, so treat it as if you didn't find it.
11772                         -- (Actually there shouldn't be any zero balances in the table,
11773                         -- but we check just to make sure.)
11774                         --
11775                         SELECT *
11776                         INTO curr_credit_bal
11777                         FROM t_credit
11778                         WHERE
11779                                 credit = fund_credit.credit
11780                                 AND balance > 0;
11781                         --
11782                         IF curr_credit_bal IS NULL THEN
11783                                 --
11784                                 -- This credit is exhausted; try the next one.
11785                                 --
11786                                 CONTINUE;
11787                         END IF;
11788                         --
11789                         --
11790                         -- At this point we have an applicable credit with some money left.
11791                         -- Now see if the relevant funding_source has any money left.
11792                         --
11793                         -- Look up the balance of the allocation for this combination of
11794                         -- fund and source.  If you find such an entry, but it has a zero
11795                         -- balance, then it's not useful, so treat it as unfound.
11796                         -- (Actually there shouldn't be any zero balances in the table,
11797                         -- but we check just to make sure.)
11798                         --
11799                         SELECT *
11800                         INTO curr_fund_source_bal
11801                         FROM t_fund_source_bal
11802                         WHERE
11803                                 fund = deb.fund
11804                                 AND source = curr_credit_bal.source
11805                                 AND balance > 0;
11806                         --
11807                         IF curr_fund_source_bal IS NULL THEN
11808                                 --
11809                                 -- This fund/source doesn't exist or is already exhausted,
11810                                 -- so we can't use this credit.  Go on to the next one.
11811                                 --
11812                                 CONTINUE;
11813                         END IF;
11814                         --
11815                         -- Convert the available balances to the currency of the fund
11816                         --
11817                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11818                                 curr_credit_bal.currency_type, deb.currency_type );
11819                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11820                                 curr_credit_bal.currency_type, deb.currency_type );
11821                         --
11822                         -- Determine how much we can attribute to this credit: the minimum
11823                         -- of the debit amount, the fund/source balance, and the
11824                         -- credit balance
11825                         --
11826                         --RAISE NOTICE '   deb bal %', debit_balance;
11827                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11828                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11829                         --
11830                         conv_attr_amount := NULL;
11831                         attr_amount := debit_balance;
11832                         --
11833                         IF attr_amount > conv_alloc_balance THEN
11834                                 attr_amount := conv_alloc_balance;
11835                                 conv_attr_amount := curr_fund_source_bal.balance;
11836                         END IF;
11837                         IF attr_amount > conv_cred_balance THEN
11838                                 attr_amount := conv_cred_balance;
11839                                 conv_attr_amount := curr_credit_bal.balance;
11840                         END IF;
11841                         --
11842                         -- If we're attributing all of one of the balances, then that's how
11843                         -- much we will deduct from the balances, and we already captured
11844                         -- that amount above.  Otherwise we must convert the amount of the
11845                         -- attribution from the currency of the fund back to the currency of
11846                         -- the funding source.
11847                         --
11848                         IF conv_attr_amount IS NULL THEN
11849                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11850                                         deb.currency_type, curr_credit_bal.currency_type );
11851                         END IF;
11852                         --
11853                         -- Insert a row to record the attribution
11854                         --
11855                         attrib_count := attrib_count + 1;
11856                         INSERT INTO acq.debit_attribution (
11857                                 id,
11858                                 fund_debit,
11859                                 debit_amount,
11860                                 funding_source_credit,
11861                                 credit_amount
11862                         ) VALUES (
11863                                 attrib_count,
11864                                 deb.id,
11865                                 attr_amount,
11866                                 curr_credit_bal.credit,
11867                                 conv_attr_amount
11868                         );
11869                         --
11870                         -- Subtract the attributed amount from the various balances
11871                         --
11872                         debit_balance := debit_balance - attr_amount;
11873                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11874                         --
11875                         IF curr_fund_source_bal.balance <= 0 THEN
11876                                 --
11877                                 -- This allocation is exhausted.  Delete it so
11878                                 -- that we don't waste time looking at it again.
11879                                 --
11880                                 DELETE FROM t_fund_source_bal
11881                                 WHERE
11882                                         fund = curr_fund_source_bal.fund
11883                                         AND source = curr_fund_source_bal.source;
11884                         ELSE
11885                                 UPDATE t_fund_source_bal
11886                                 SET balance = balance - conv_attr_amount
11887                                 WHERE
11888                                         fund = curr_fund_source_bal.fund
11889                                         AND source = curr_fund_source_bal.source;
11890                         END IF;
11891                         --
11892                         IF curr_credit_bal.balance <= 0 THEN
11893                                 --
11894                                 -- This funding source credit is exhausted.  Delete it
11895                                 -- so that we don't waste time looking at it again.
11896                                 --
11897                                 --DELETE FROM t_credit
11898                                 --WHERE
11899                                 --      credit = curr_credit_bal.credit;
11900                                 --
11901                                 DELETE FROM t_fund_credit
11902                                 WHERE
11903                                         credit = curr_credit_bal.credit;
11904                         ELSE
11905                                 UPDATE t_credit
11906                                 SET balance = curr_credit_bal.balance
11907                                 WHERE
11908                                         credit = curr_credit_bal.credit;
11909                         END IF;
11910                         --
11911                         -- Are we done with this debit yet?
11912                         --
11913                         IF debit_balance <= 0 THEN
11914                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
11915                         END IF;
11916                 END LOOP;       -- End loop over credits
11917                 --
11918                 IF debit_balance <> 0 THEN
11919                         --
11920                         -- We weren't able to attribute this debit, or at least not
11921                         -- all of it.  Insert a row for the unattributed balance.
11922                         --
11923                         attrib_count := attrib_count + 1;
11924                         INSERT INTO acq.debit_attribution (
11925                                 id,
11926                                 fund_debit,
11927                                 debit_amount,
11928                                 funding_source_credit,
11929                                 credit_amount
11930                         ) VALUES (
11931                                 attrib_count,
11932                                 deb.id,
11933                                 debit_balance,
11934                                 NULL,
11935                                 NULL
11936                         );
11937                 END IF;
11938         END LOOP;   -- End of loop over debits
11939 END;
11940 $$ LANGUAGE 'plpgsql';
11941
11942 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
11943 DECLARE
11944     query TEXT;
11945     output TEXT;
11946 BEGIN
11947     query := $q$
11948         SELECT  regexp_replace(
11949                     oils_xpath_string(
11950                         $q$ || quote_literal($3) || $q$,
11951                         marc,
11952                         ' '
11953                     ),
11954                     $q$ || quote_literal($4) || $q$,
11955                     '',
11956                     'g')
11957           FROM  $q$ || $1 || $q$
11958           WHERE id = $q$ || $2;
11959
11960     EXECUTE query INTO output;
11961
11962     -- RAISE NOTICE 'query: %, output; %', query, output;
11963
11964     RETURN output;
11965 END;
11966 $$ LANGUAGE PLPGSQL IMMUTABLE;
11967
11968 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
11969     SELECT extract_marc_field($1,$2,$3,'');
11970 $$ LANGUAGE SQL IMMUTABLE;
11971
11972 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
11973 DECLARE
11974     moved_objects INT := 0;
11975     source_cn     asset.call_number%ROWTYPE;
11976     target_cn     asset.call_number%ROWTYPE;
11977     metarec       metabib.metarecord%ROWTYPE;
11978     hold          action.hold_request%ROWTYPE;
11979     ser_rec       serial.record_entry%ROWTYPE;
11980     uri_count     INT := 0;
11981     counter       INT := 0;
11982     uri_datafield TEXT;
11983     uri_text      TEXT := '';
11984 BEGIN
11985
11986     -- move any 856 entries on records that have at least one MARC-mapped URI entry
11987     SELECT  INTO uri_count COUNT(*)
11988       FROM  asset.uri_call_number_map m
11989             JOIN asset.call_number cn ON (m.call_number = cn.id)
11990       WHERE cn.record = source_record;
11991
11992     IF uri_count > 0 THEN
11993
11994         SELECT  COUNT(*) INTO counter
11995           FROM  oils_xpath_table(
11996                     'id',
11997                     'marc',
11998                     'biblio.record_entry',
11999                     '//*[@tag="856"]',
12000                     'id=' || source_record
12001                 ) as t(i int,c text);
12002
12003         FOR i IN 1 .. counter LOOP
12004             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
12005                         ' tag="856"' || 
12006                         ' ind1="' || FIRST(ind1) || '"'  || 
12007                         ' ind2="' || FIRST(ind2) || '">' || 
12008                         array_to_string(
12009                             array_accum(
12010                                 '<subfield code="' || subfield || '">' ||
12011                                 regexp_replace(
12012                                     regexp_replace(
12013                                         regexp_replace(data,'&','&amp;','g'),
12014                                         '>', '&gt;', 'g'
12015                                     ),
12016                                     '<', '&lt;', 'g'
12017                                 ) || '</subfield>'
12018                             ), ''
12019                         ) || '</datafield>' INTO uri_datafield
12020               FROM  oils_xpath_table(
12021                         'id',
12022                         'marc',
12023                         'biblio.record_entry',
12024                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
12025                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
12026                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
12027                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
12028                         'id=' || source_record
12029                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
12030
12031             uri_text := uri_text || uri_datafield;
12032         END LOOP;
12033
12034         IF uri_text <> '' THEN
12035             UPDATE  biblio.record_entry
12036               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
12037               WHERE id = target_record;
12038         END IF;
12039
12040     END IF;
12041
12042     -- Find and move metarecords to the target record
12043     SELECT  INTO metarec *
12044       FROM  metabib.metarecord
12045       WHERE master_record = source_record;
12046
12047     IF FOUND THEN
12048         UPDATE  metabib.metarecord
12049           SET   master_record = target_record,
12050             mods = NULL
12051           WHERE id = metarec.id;
12052
12053         moved_objects := moved_objects + 1;
12054     END IF;
12055
12056     -- Find call numbers attached to the source ...
12057     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
12058
12059         SELECT  INTO target_cn *
12060           FROM  asset.call_number
12061           WHERE label = source_cn.label
12062             AND owning_lib = source_cn.owning_lib
12063             AND record = target_record;
12064
12065         -- ... and if there's a conflicting one on the target ...
12066         IF FOUND THEN
12067
12068             -- ... move the copies to that, and ...
12069             UPDATE  asset.copy
12070               SET   call_number = target_cn.id
12071               WHERE call_number = source_cn.id;
12072
12073             -- ... move V holds to the move-target call number
12074             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
12075
12076                 UPDATE  action.hold_request
12077                   SET   target = target_cn.id
12078                   WHERE id = hold.id;
12079
12080                 moved_objects := moved_objects + 1;
12081             END LOOP;
12082
12083         -- ... if not ...
12084         ELSE
12085             -- ... just move the call number to the target record
12086             UPDATE  asset.call_number
12087               SET   record = target_record
12088               WHERE id = source_cn.id;
12089         END IF;
12090
12091         moved_objects := moved_objects + 1;
12092     END LOOP;
12093
12094     -- Find T holds targeting the source record ...
12095     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
12096
12097         -- ... and move them to the target record
12098         UPDATE  action.hold_request
12099           SET   target = target_record
12100           WHERE id = hold.id;
12101
12102         moved_objects := moved_objects + 1;
12103     END LOOP;
12104
12105     -- Find serial records targeting the source record ...
12106     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
12107         -- ... and move them to the target record
12108         UPDATE  serial.record_entry
12109           SET   record = target_record
12110           WHERE id = ser_rec.id;
12111
12112         moved_objects := moved_objects + 1;
12113     END LOOP;
12114
12115     -- Finally, "delete" the source record
12116     DELETE FROM biblio.record_entry WHERE id = source_record;
12117
12118     -- That's all, folks!
12119     RETURN moved_objects;
12120 END;
12121 $func$ LANGUAGE plpgsql;
12122
12123 CREATE OR REPLACE FUNCTION acq.transfer_fund(
12124         old_fund   IN INT,
12125         old_amount IN NUMERIC,     -- in currency of old fund
12126         new_fund   IN INT,
12127         new_amount IN NUMERIC,     -- in currency of new fund
12128         user_id    IN INT,
12129         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
12130         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
12131 ) RETURNS VOID AS $$
12132 /* -------------------------------------------------------------------------------
12133
12134 Function to transfer money from one fund to another.
12135
12136 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12137 negative amount for the old (losing) fund and a positive amount for the new
12138 (gaining) fund.  In some cases there may be more than one such pair of entries
12139 in order to pull the money from different funding sources, or more specifically
12140 from different funding source credits.  For each such pair there is also an
12141 entry in acq.fund_transfer.
12142
12143 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12144 choose a funding source for the transferred money to come from.  This choice
12145 must meet two constraints, so far as possible:
12146
12147 1. The amount transferred from a given funding source must not exceed the
12148 amount allocated to the old fund by the funding source.  To that end we
12149 compare the amount being transferred to the amount allocated.
12150
12151 2. We shouldn't transfer money that has already been spent or encumbered, as
12152 defined by the funding attribution process.  We attribute expenses to the
12153 oldest funding source credits first.  In order to avoid transferring that
12154 attributed money, we reverse the priority, transferring from the newest funding
12155 source credits first.  There can be no guarantee that this approach will
12156 avoid overcommitting a fund, but no other approach can do any better.
12157
12158 In this context the age of a funding source credit is defined by the
12159 deadline_date for credits with deadline_dates, and by the effective_date for
12160 credits without deadline_dates, with the proviso that credits with deadline_dates
12161 are all considered "older" than those without.
12162
12163 ----------
12164
12165 In the signature for this function, there is one last parameter commented out,
12166 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12167 driving the main loop has an OR clause commented out, which references the
12168 funding_source_in parameter.
12169
12170 If these lines are uncommented, this function will allow the user optionally to
12171 restrict a fund transfer to a specified funding source.  If the source
12172 parameter is left NULL, then there will be no such restriction.
12173
12174 ------------------------------------------------------------------------------- */ 
12175 DECLARE
12176         same_currency      BOOLEAN;
12177         currency_ratio     NUMERIC;
12178         old_fund_currency  TEXT;
12179         old_remaining      NUMERIC;  -- in currency of old fund
12180         new_fund_currency  TEXT;
12181         new_fund_active    BOOLEAN;
12182         new_remaining      NUMERIC;  -- in currency of new fund
12183         curr_old_amt       NUMERIC;  -- in currency of old fund
12184         curr_new_amt       NUMERIC;  -- in currency of new fund
12185         source_addition    NUMERIC;  -- in currency of funding source
12186         source_deduction   NUMERIC;  -- in currency of funding source
12187         orig_allocated_amt NUMERIC;  -- in currency of funding source
12188         allocated_amt      NUMERIC;  -- in currency of fund
12189         source             RECORD;
12190 BEGIN
12191         --
12192         -- Sanity checks
12193         --
12194         IF old_fund IS NULL THEN
12195                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12196         END IF;
12197         --
12198         IF old_amount IS NULL THEN
12199                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12200         END IF;
12201         --
12202         -- The new fund and its amount must be both NULL or both not NULL.
12203         --
12204         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12205                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12206         END IF;
12207         --
12208         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12209                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12210         END IF;
12211         --
12212         IF user_id IS NULL THEN
12213                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12214         END IF;
12215         --
12216         -- Initialize the amounts to be transferred, each denominated
12217         -- in the currency of its respective fund.  They will be
12218         -- reduced on each iteration of the loop.
12219         --
12220         old_remaining := old_amount;
12221         new_remaining := new_amount;
12222         --
12223         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12224         --      old_amount, old_fund, new_amount, new_fund;
12225         --
12226         -- Get the currency types of the old and new funds.
12227         --
12228         SELECT
12229                 currency_type
12230         INTO
12231                 old_fund_currency
12232         FROM
12233                 acq.fund
12234         WHERE
12235                 id = old_fund;
12236         --
12237         IF old_fund_currency IS NULL THEN
12238                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12239         END IF;
12240         --
12241         IF new_fund IS NOT NULL THEN
12242                 SELECT
12243                         currency_type,
12244                         active
12245                 INTO
12246                         new_fund_currency,
12247                         new_fund_active
12248                 FROM
12249                         acq.fund
12250                 WHERE
12251                         id = new_fund;
12252                 --
12253                 IF new_fund_currency IS NULL THEN
12254                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12255                 ELSIF NOT new_fund_active THEN
12256                         --
12257                         -- No point in putting money into a fund from whence you can't spend it
12258                         --
12259                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12260                 END IF;
12261                 --
12262                 IF new_amount = old_amount THEN
12263                         same_currency := true;
12264                         currency_ratio := 1;
12265                 ELSE
12266                         --
12267                         -- We'll have to translate currency between funds.  We presume that
12268                         -- the calling code has already applied an appropriate exchange rate,
12269                         -- so we'll apply the same conversion to each sub-transfer.
12270                         --
12271                         same_currency := false;
12272                         currency_ratio := new_amount / old_amount;
12273                 END IF;
12274         END IF;
12275         --
12276         -- Identify the funding source(s) from which we want to transfer the money.
12277         -- The principle is that we want to transfer the newest money first, because
12278         -- we spend the oldest money first.  The priority for spending is defined
12279         -- by a sort of the view acq.ordered_funding_source_credit.
12280         --
12281         FOR source in
12282                 SELECT
12283                         ofsc.id,
12284                         ofsc.funding_source,
12285                         ofsc.amount,
12286                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12287                                 AS converted_amt,
12288                         fs.currency_type
12289                 FROM
12290                         acq.ordered_funding_source_credit AS ofsc,
12291                         acq.funding_source fs
12292                 WHERE
12293                         ofsc.funding_source = fs.id
12294                         and ofsc.funding_source IN
12295                         (
12296                                 SELECT funding_source
12297                                 FROM acq.fund_allocation
12298                                 WHERE fund = old_fund
12299                         )
12300                         -- and
12301                         -- (
12302                         --      ofsc.funding_source = funding_source_in
12303                         --      OR funding_source_in IS NULL
12304                         -- )
12305                 ORDER BY
12306                         ofsc.sort_priority desc,
12307                         ofsc.sort_date desc,
12308                         ofsc.id desc
12309         LOOP
12310                 --
12311                 -- Determine how much money the old fund got from this funding source,
12312                 -- denominated in the currency types of the source and of the fund.
12313                 -- This result may reflect transfers from previous iterations.
12314                 --
12315                 SELECT
12316                         COALESCE( sum( amount ), 0 ),
12317                         COALESCE( sum( amount )
12318                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12319                 INTO
12320                         orig_allocated_amt,     -- in currency of the source
12321                         allocated_amt           -- in currency of the old fund
12322                 FROM
12323                         acq.fund_allocation
12324                 WHERE
12325                         fund = old_fund
12326                         and funding_source = source.funding_source;
12327                 --      
12328                 -- Determine how much to transfer from this credit, in the currency
12329                 -- of the fund.   Begin with the amount remaining to be attributed:
12330                 --
12331                 curr_old_amt := old_remaining;
12332                 --
12333                 -- Can't attribute more than was allocated from the fund:
12334                 --
12335                 IF curr_old_amt > allocated_amt THEN
12336                         curr_old_amt := allocated_amt;
12337                 END IF;
12338                 --
12339                 -- Can't attribute more than the amount of the current credit:
12340                 --
12341                 IF curr_old_amt > source.converted_amt THEN
12342                         curr_old_amt := source.converted_amt;
12343                 END IF;
12344                 --
12345                 curr_old_amt := trunc( curr_old_amt, 2 );
12346                 --
12347                 old_remaining := old_remaining - curr_old_amt;
12348                 --
12349                 -- Determine the amount to be deducted, if any,
12350                 -- from the old allocation.
12351                 --
12352                 IF old_remaining > 0 THEN
12353                         --
12354                         -- In this case we're using the whole allocation, so use that
12355                         -- amount directly instead of applying a currency translation
12356                         -- and thereby inviting round-off errors.
12357                         --
12358                         source_deduction := - orig_allocated_amt;
12359                 ELSE 
12360                         source_deduction := trunc(
12361                                 ( - curr_old_amt ) *
12362                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12363                                 2 );
12364                 END IF;
12365                 --
12366                 IF source_deduction <> 0 THEN
12367                         --
12368                         -- Insert negative allocation for old fund in fund_allocation,
12369                         -- converted into the currency of the funding source
12370                         --
12371                         INSERT INTO acq.fund_allocation (
12372                                 funding_source,
12373                                 fund,
12374                                 amount,
12375                                 allocator,
12376                                 note
12377                         ) VALUES (
12378                                 source.funding_source,
12379                                 old_fund,
12380                                 source_deduction,
12381                                 user_id,
12382                                 'Transfer to fund ' || new_fund
12383                         );
12384                 END IF;
12385                 --
12386                 IF new_fund IS NOT NULL THEN
12387                         --
12388                         -- Determine how much to add to the new fund, in
12389                         -- its currency, and how much remains to be added:
12390                         --
12391                         IF same_currency THEN
12392                                 curr_new_amt := curr_old_amt;
12393                         ELSE
12394                                 IF old_remaining = 0 THEN
12395                                         --
12396                                         -- This is the last iteration, so nothing should be left
12397                                         --
12398                                         curr_new_amt := new_remaining;
12399                                         new_remaining := 0;
12400                                 ELSE
12401                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12402                                         new_remaining := new_remaining - curr_new_amt;
12403                                 END IF;
12404                         END IF;
12405                         --
12406                         -- Determine how much to add, if any,
12407                         -- to the new fund's allocation.
12408                         --
12409                         IF old_remaining > 0 THEN
12410                                 --
12411                                 -- In this case we're using the whole allocation, so use that amount
12412                                 -- amount directly instead of applying a currency translation and
12413                                 -- thereby inviting round-off errors.
12414                                 --
12415                                 source_addition := orig_allocated_amt;
12416                         ELSIF source.currency_type = old_fund_currency THEN
12417                                 --
12418                                 -- In this case we don't need a round trip currency translation,
12419                                 -- thereby inviting round-off errors:
12420                                 --
12421                                 source_addition := curr_old_amt;
12422                         ELSE 
12423                                 source_addition := trunc(
12424                                         curr_new_amt *
12425                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12426                                         2 );
12427                         END IF;
12428                         --
12429                         IF source_addition <> 0 THEN
12430                                 --
12431                                 -- Insert positive allocation for new fund in fund_allocation,
12432                                 -- converted to the currency of the founding source
12433                                 --
12434                                 INSERT INTO acq.fund_allocation (
12435                                         funding_source,
12436                                         fund,
12437                                         amount,
12438                                         allocator,
12439                                         note
12440                                 ) VALUES (
12441                                         source.funding_source,
12442                                         new_fund,
12443                                         source_addition,
12444                                         user_id,
12445                                         'Transfer from fund ' || old_fund
12446                                 );
12447                         END IF;
12448                 END IF;
12449                 --
12450                 IF trunc( curr_old_amt, 2 ) <> 0
12451                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12452                         --
12453                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12454                         --
12455                         INSERT INTO acq.fund_transfer (
12456                                 src_fund,
12457                                 src_amount,
12458                                 dest_fund,
12459                                 dest_amount,
12460                                 transfer_user,
12461                                 note,
12462                                 funding_source_credit
12463                         ) VALUES (
12464                                 old_fund,
12465                                 trunc( curr_old_amt, 2 ),
12466                                 new_fund,
12467                                 trunc( curr_new_amt, 2 ),
12468                                 user_id,
12469                                 xfer_note,
12470                                 source.id
12471                         );
12472                 END IF;
12473                 --
12474                 if old_remaining <= 0 THEN
12475                         EXIT;                   -- Nothing more to be transferred
12476                 END IF;
12477         END LOOP;
12478 END;
12479 $$ LANGUAGE plpgsql;
12480
12481 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
12482         old_year INTEGER,
12483         user_id INTEGER,
12484         org_unit_id INTEGER
12485 ) RETURNS VOID AS $$
12486 DECLARE
12487 --
12488 new_id      INT;
12489 old_fund    RECORD;
12490 org_found   BOOLEAN;
12491 --
12492 BEGIN
12493         --
12494         -- Sanity checks
12495         --
12496         IF old_year IS NULL THEN
12497                 RAISE EXCEPTION 'Input year argument is NULL';
12498         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12499                 RAISE EXCEPTION 'Input year is out of range';
12500         END IF;
12501         --
12502         IF user_id IS NULL THEN
12503                 RAISE EXCEPTION 'Input user id argument is NULL';
12504         END IF;
12505         --
12506         IF org_unit_id IS NULL THEN
12507                 RAISE EXCEPTION 'Org unit id argument is NULL';
12508         ELSE
12509                 SELECT TRUE INTO org_found
12510                 FROM actor.org_unit
12511                 WHERE id = org_unit_id;
12512                 --
12513                 IF org_found IS NULL THEN
12514                         RAISE EXCEPTION 'Org unit id is invalid';
12515                 END IF;
12516         END IF;
12517         --
12518         -- Loop over the applicable funds
12519         --
12520         FOR old_fund in SELECT * FROM acq.fund
12521         WHERE
12522                 year = old_year
12523                 AND propagate
12524                 AND org = org_unit_id
12525         LOOP
12526                 BEGIN
12527                         INSERT INTO acq.fund (
12528                                 org,
12529                                 name,
12530                                 year,
12531                                 currency_type,
12532                                 code,
12533                                 rollover,
12534                                 propagate,
12535                                 balance_warning_percent,
12536                                 balance_stop_percent
12537                         ) VALUES (
12538                                 old_fund.org,
12539                                 old_fund.name,
12540                                 old_year + 1,
12541                                 old_fund.currency_type,
12542                                 old_fund.code,
12543                                 old_fund.rollover,
12544                                 true,
12545                                 old_fund.balance_warning_percent,
12546                                 old_fund.balance_stop_percent
12547                         )
12548                         RETURNING id INTO new_id;
12549                 EXCEPTION
12550                         WHEN unique_violation THEN
12551                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12552                                 CONTINUE;
12553                 END;
12554                 --RAISE NOTICE 'Propagating fund % to fund %',
12555                 --      old_fund.code, new_id;
12556         END LOOP;
12557 END;
12558 $$ LANGUAGE plpgsql;
12559
12560 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12561         old_year INTEGER,
12562         user_id INTEGER,
12563         org_unit_id INTEGER
12564 ) RETURNS VOID AS $$
12565 DECLARE
12566 --
12567 new_id      INT;
12568 old_fund    RECORD;
12569 org_found   BOOLEAN;
12570 --
12571 BEGIN
12572         --
12573         -- Sanity checks
12574         --
12575         IF old_year IS NULL THEN
12576                 RAISE EXCEPTION 'Input year argument is NULL';
12577         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12578                 RAISE EXCEPTION 'Input year is out of range';
12579         END IF;
12580         --
12581         IF user_id IS NULL THEN
12582                 RAISE EXCEPTION 'Input user id argument is NULL';
12583         END IF;
12584         --
12585         IF org_unit_id IS NULL THEN
12586                 RAISE EXCEPTION 'Org unit id argument is NULL';
12587         ELSE
12588                 SELECT TRUE INTO org_found
12589                 FROM actor.org_unit
12590                 WHERE id = org_unit_id;
12591                 --
12592                 IF org_found IS NULL THEN
12593                         RAISE EXCEPTION 'Org unit id is invalid';
12594                 END IF;
12595         END IF;
12596         --
12597         -- Loop over the applicable funds
12598         --
12599         FOR old_fund in SELECT * FROM acq.fund
12600         WHERE
12601                 year = old_year
12602                 AND propagate
12603                 AND org in (
12604                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12605                 )
12606         LOOP
12607                 BEGIN
12608                         INSERT INTO acq.fund (
12609                                 org,
12610                                 name,
12611                                 year,
12612                                 currency_type,
12613                                 code,
12614                                 rollover,
12615                                 propagate,
12616                                 balance_warning_percent,
12617                                 balance_stop_percent
12618                         ) VALUES (
12619                                 old_fund.org,
12620                                 old_fund.name,
12621                                 old_year + 1,
12622                                 old_fund.currency_type,
12623                                 old_fund.code,
12624                                 old_fund.rollover,
12625                                 true,
12626                                 old_fund.balance_warning_percent,
12627                                 old_fund.balance_stop_percent
12628                         )
12629                         RETURNING id INTO new_id;
12630                 EXCEPTION
12631                         WHEN unique_violation THEN
12632                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12633                                 CONTINUE;
12634                 END;
12635                 --RAISE NOTICE 'Propagating fund % to fund %',
12636                 --      old_fund.code, new_id;
12637         END LOOP;
12638 END;
12639 $$ LANGUAGE plpgsql;
12640
12641 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12642         old_year INTEGER,
12643         user_id INTEGER,
12644         org_unit_id INTEGER
12645 ) RETURNS VOID AS $$
12646 DECLARE
12647 --
12648 new_fund    INT;
12649 new_year    INT := old_year + 1;
12650 org_found   BOOL;
12651 xfer_amount NUMERIC;
12652 roll_fund   RECORD;
12653 deb         RECORD;
12654 detail      RECORD;
12655 --
12656 BEGIN
12657         --
12658         -- Sanity checks
12659         --
12660         IF old_year IS NULL THEN
12661                 RAISE EXCEPTION 'Input year argument is NULL';
12662     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12663         RAISE EXCEPTION 'Input year is out of range';
12664         END IF;
12665         --
12666         IF user_id IS NULL THEN
12667                 RAISE EXCEPTION 'Input user id argument is NULL';
12668         END IF;
12669         --
12670         IF org_unit_id IS NULL THEN
12671                 RAISE EXCEPTION 'Org unit id argument is NULL';
12672         ELSE
12673                 --
12674                 -- Validate the org unit
12675                 --
12676                 SELECT TRUE
12677                 INTO org_found
12678                 FROM actor.org_unit
12679                 WHERE id = org_unit_id;
12680                 --
12681                 IF org_found IS NULL THEN
12682                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12683                 END IF;
12684         END IF;
12685         --
12686         -- Loop over the propagable funds to identify the details
12687         -- from the old fund plus the id of the new one, if it exists.
12688         --
12689         FOR roll_fund in
12690         SELECT
12691             oldf.id AS old_fund,
12692             oldf.org,
12693             oldf.name,
12694             oldf.currency_type,
12695             oldf.code,
12696                 oldf.rollover,
12697             newf.id AS new_fund_id
12698         FROM
12699         acq.fund AS oldf
12700         LEFT JOIN acq.fund AS newf
12701                 ON ( oldf.code = newf.code )
12702         WHERE
12703                     oldf.org = org_unit_id
12704                 and oldf.year = old_year
12705                 and oldf.propagate
12706         and newf.year = new_year
12707         LOOP
12708                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12709                 --
12710                 IF roll_fund.new_fund_id IS NULL THEN
12711                         --
12712                         -- The old fund hasn't been propagated yet.  Propagate it now.
12713                         --
12714                         INSERT INTO acq.fund (
12715                                 org,
12716                                 name,
12717                                 year,
12718                                 currency_type,
12719                                 code,
12720                                 rollover,
12721                                 propagate,
12722                                 balance_warning_percent,
12723                                 balance_stop_percent
12724                         ) VALUES (
12725                                 roll_fund.org,
12726                                 roll_fund.name,
12727                                 new_year,
12728                                 roll_fund.currency_type,
12729                                 roll_fund.code,
12730                                 true,
12731                                 true,
12732                                 roll_fund.balance_warning_percent,
12733                                 roll_fund.balance_stop_percent
12734                         )
12735                         RETURNING id INTO new_fund;
12736                 ELSE
12737                         new_fund = roll_fund.new_fund_id;
12738                 END IF;
12739                 --
12740                 -- Determine the amount to transfer
12741                 --
12742                 SELECT amount
12743                 INTO xfer_amount
12744                 FROM acq.fund_spent_balance
12745                 WHERE fund = roll_fund.old_fund;
12746                 --
12747                 IF xfer_amount <> 0 THEN
12748                         IF roll_fund.rollover THEN
12749                                 --
12750                                 -- Transfer balance from old fund to new
12751                                 --
12752                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12753                                 --
12754                                 PERFORM acq.transfer_fund(
12755                                         roll_fund.old_fund,
12756                                         xfer_amount,
12757                                         new_fund,
12758                                         xfer_amount,
12759                                         user_id,
12760                                         'Rollover'
12761                                 );
12762                         ELSE
12763                                 --
12764                                 -- Transfer balance from old fund to the void
12765                                 --
12766                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12767                                 --
12768                                 PERFORM acq.transfer_fund(
12769                                         roll_fund.old_fund,
12770                                         xfer_amount,
12771                                         NULL,
12772                                         NULL,
12773                                         user_id,
12774                                         'Rollover'
12775                                 );
12776                         END IF;
12777                 END IF;
12778                 --
12779                 IF roll_fund.rollover THEN
12780                         --
12781                         -- Move any lineitems from the old fund to the new one
12782                         -- where the associated debit is an encumbrance.
12783                         --
12784                         -- Any other tables tying expenditure details to funds should
12785                         -- receive similar treatment.  At this writing there are none.
12786                         --
12787                         UPDATE acq.lineitem_detail
12788                         SET fund = new_fund
12789                         WHERE
12790                         fund = roll_fund.old_fund -- this condition may be redundant
12791                         AND fund_debit in
12792                         (
12793                                 SELECT id
12794                                 FROM acq.fund_debit
12795                                 WHERE
12796                                 fund = roll_fund.old_fund
12797                                 AND encumbrance
12798                         );
12799                         --
12800                         -- Move encumbrance debits from the old fund to the new fund
12801                         --
12802                         UPDATE acq.fund_debit
12803                         SET fund = new_fund
12804                         wHERE
12805                                 fund = roll_fund.old_fund
12806                                 AND encumbrance;
12807                 END IF;
12808                 --
12809                 -- Mark old fund as inactive, now that we've closed it
12810                 --
12811                 UPDATE acq.fund
12812                 SET active = FALSE
12813                 WHERE id = roll_fund.old_fund;
12814         END LOOP;
12815 END;
12816 $$ LANGUAGE plpgsql;
12817
12818 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12819         old_year INTEGER,
12820         user_id INTEGER,
12821         org_unit_id INTEGER
12822 ) RETURNS VOID AS $$
12823 DECLARE
12824 --
12825 new_fund    INT;
12826 new_year    INT := old_year + 1;
12827 org_found   BOOL;
12828 xfer_amount NUMERIC;
12829 roll_fund   RECORD;
12830 deb         RECORD;
12831 detail      RECORD;
12832 --
12833 BEGIN
12834         --
12835         -- Sanity checks
12836         --
12837         IF old_year IS NULL THEN
12838                 RAISE EXCEPTION 'Input year argument is NULL';
12839     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12840         RAISE EXCEPTION 'Input year is out of range';
12841         END IF;
12842         --
12843         IF user_id IS NULL THEN
12844                 RAISE EXCEPTION 'Input user id argument is NULL';
12845         END IF;
12846         --
12847         IF org_unit_id IS NULL THEN
12848                 RAISE EXCEPTION 'Org unit id argument is NULL';
12849         ELSE
12850                 --
12851                 -- Validate the org unit
12852                 --
12853                 SELECT TRUE
12854                 INTO org_found
12855                 FROM actor.org_unit
12856                 WHERE id = org_unit_id;
12857                 --
12858                 IF org_found IS NULL THEN
12859                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12860                 END IF;
12861         END IF;
12862         --
12863         -- Loop over the propagable funds to identify the details
12864         -- from the old fund plus the id of the new one, if it exists.
12865         --
12866         FOR roll_fund in
12867         SELECT
12868             oldf.id AS old_fund,
12869             oldf.org,
12870             oldf.name,
12871             oldf.currency_type,
12872             oldf.code,
12873                 oldf.rollover,
12874             newf.id AS new_fund_id
12875         FROM
12876         acq.fund AS oldf
12877         LEFT JOIN acq.fund AS newf
12878                 ON ( oldf.code = newf.code )
12879         WHERE
12880                     oldf.year = old_year
12881                 AND oldf.propagate
12882         AND newf.year = new_year
12883                 AND oldf.org in (
12884                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12885                 )
12886         LOOP
12887                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12888                 --
12889                 IF roll_fund.new_fund_id IS NULL THEN
12890                         --
12891                         -- The old fund hasn't been propagated yet.  Propagate it now.
12892                         --
12893                         INSERT INTO acq.fund (
12894                                 org,
12895                                 name,
12896                                 year,
12897                                 currency_type,
12898                                 code,
12899                                 rollover,
12900                                 propagate,
12901                                 balance_warning_percent,
12902                                 balance_stop_percent
12903                         ) VALUES (
12904                                 roll_fund.org,
12905                                 roll_fund.name,
12906                                 new_year,
12907                                 roll_fund.currency_type,
12908                                 roll_fund.code,
12909                                 true,
12910                                 true,
12911                                 roll_fund.balance_warning_percent,
12912                                 roll_fund.balance_stop_percent
12913                         )
12914                         RETURNING id INTO new_fund;
12915                 ELSE
12916                         new_fund = roll_fund.new_fund_id;
12917                 END IF;
12918                 --
12919                 -- Determine the amount to transfer
12920                 --
12921                 SELECT amount
12922                 INTO xfer_amount
12923                 FROM acq.fund_spent_balance
12924                 WHERE fund = roll_fund.old_fund;
12925                 --
12926                 IF xfer_amount <> 0 THEN
12927                         IF roll_fund.rollover THEN
12928                                 --
12929                                 -- Transfer balance from old fund to new
12930                                 --
12931                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12932                                 --
12933                                 PERFORM acq.transfer_fund(
12934                                         roll_fund.old_fund,
12935                                         xfer_amount,
12936                                         new_fund,
12937                                         xfer_amount,
12938                                         user_id,
12939                                         'Rollover'
12940                                 );
12941                         ELSE
12942                                 --
12943                                 -- Transfer balance from old fund to the void
12944                                 --
12945                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12946                                 --
12947                                 PERFORM acq.transfer_fund(
12948                                         roll_fund.old_fund,
12949                                         xfer_amount,
12950                                         NULL,
12951                                         NULL,
12952                                         user_id,
12953                                         'Rollover'
12954                                 );
12955                         END IF;
12956                 END IF;
12957                 --
12958                 IF roll_fund.rollover THEN
12959                         --
12960                         -- Move any lineitems from the old fund to the new one
12961                         -- where the associated debit is an encumbrance.
12962                         --
12963                         -- Any other tables tying expenditure details to funds should
12964                         -- receive similar treatment.  At this writing there are none.
12965                         --
12966                         UPDATE acq.lineitem_detail
12967                         SET fund = new_fund
12968                         WHERE
12969                         fund = roll_fund.old_fund -- this condition may be redundant
12970                         AND fund_debit in
12971                         (
12972                                 SELECT id
12973                                 FROM acq.fund_debit
12974                                 WHERE
12975                                 fund = roll_fund.old_fund
12976                                 AND encumbrance
12977                         );
12978                         --
12979                         -- Move encumbrance debits from the old fund to the new fund
12980                         --
12981                         UPDATE acq.fund_debit
12982                         SET fund = new_fund
12983                         wHERE
12984                                 fund = roll_fund.old_fund
12985                                 AND encumbrance;
12986                 END IF;
12987                 --
12988                 -- Mark old fund as inactive, now that we've closed it
12989                 --
12990                 UPDATE acq.fund
12991                 SET active = FALSE
12992                 WHERE id = roll_fund.old_fund;
12993         END LOOP;
12994 END;
12995 $$ LANGUAGE plpgsql;
12996
12997 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
12998     SELECT regexp_replace($1, ',', '', 'g');
12999 $$ LANGUAGE SQL STRICT IMMUTABLE;
13000
13001 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
13002     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
13003 $$ LANGUAGE SQL STRICT IMMUTABLE;
13004
13005 CREATE TABLE acq.distribution_formula_application (
13006     id BIGSERIAL PRIMARY KEY,
13007     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
13008     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
13009     formula INT NOT NULL
13010         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
13011     lineitem INT NOT NULL
13012         REFERENCES acq.lineitem( id )
13013                 ON DELETE CASCADE
13014                 DEFERRABLE INITIALLY DEFERRED
13015 );
13016
13017 CREATE INDEX acqdfa_df_idx
13018     ON acq.distribution_formula_application(formula);
13019 CREATE INDEX acqdfa_li_idx
13020     ON acq.distribution_formula_application(lineitem);
13021 CREATE INDEX acqdfa_creator_idx
13022     ON acq.distribution_formula_application(creator);
13023
13024 CREATE TABLE acq.user_request_type (
13025     id      SERIAL  PRIMARY KEY,
13026     label   TEXT    NOT NULL UNIQUE -- i18n-ize
13027 );
13028
13029 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
13030 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
13031 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
13032 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
13033 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
13034
13035 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
13036
13037 CREATE TABLE acq.cancel_reason (
13038         id            SERIAL            PRIMARY KEY,
13039         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
13040                                         DEFERRABLE INITIALLY DEFERRED,
13041         label         TEXT              NOT NULL,
13042         description   TEXT              NOT NULL,
13043         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
13044         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
13045 );
13046
13047 -- Reserve ids 1-999 for stock reasons
13048 -- Reserve ids 1000-1999 for EDI reasons
13049 -- 2000+ are available for staff to create
13050
13051 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
13052
13053 CREATE TABLE acq.user_request (
13054     id                  SERIAL  PRIMARY KEY,
13055     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
13056     hold                BOOL    NOT NULL DEFAULT TRUE,
13057
13058     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
13059     holdable_formats    TEXT,           -- nullable, for use in hold creation
13060     phone_notify        TEXT,
13061     email_notify        BOOL    NOT NULL DEFAULT TRUE,
13062     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
13063     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
13064     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
13065     need_before         TIMESTAMPTZ,    -- don't create holds after this
13066     max_fee             TEXT,
13067
13068     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
13069     isxn                TEXT,
13070     title               TEXT,
13071     volume              TEXT,
13072     author              TEXT,
13073     article_title       TEXT,
13074     article_pages       TEXT,
13075     publisher           TEXT,
13076     location            TEXT,
13077     pubdate             TEXT,
13078     mentioned           TEXT,
13079     other_info          TEXT,
13080         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
13081                                              DEFERRABLE INITIALLY DEFERRED
13082 );
13083
13084 CREATE TABLE acq.lineitem_alert_text (
13085         id               SERIAL         PRIMARY KEY,
13086         code             TEXT           NOT NULL,
13087         description      TEXT,
13088         owning_lib       INT            NOT NULL
13089                                         REFERENCES actor.org_unit(id)
13090                                         DEFERRABLE INITIALLY DEFERRED,
13091         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
13092 );
13093
13094 ALTER TABLE acq.lineitem_note
13095         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
13096                                          DEFERRABLE INITIALLY DEFERRED;
13097
13098 -- add ON DELETE CASCADE clause
13099
13100 ALTER TABLE acq.lineitem_note
13101         DROP CONSTRAINT lineitem_note_lineitem_fkey;
13102
13103 ALTER TABLE acq.lineitem_note
13104         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13105                 ON DELETE CASCADE
13106                 DEFERRABLE INITIALLY DEFERRED;
13107
13108 ALTER TABLE acq.lineitem_note
13109         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13110
13111 CREATE TABLE acq.invoice_method (
13112     code    TEXT    PRIMARY KEY,
13113     name    TEXT    NOT NULL -- i18n-ize
13114 );
13115 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
13116 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
13117
13118 CREATE TABLE acq.invoice_payment_method (
13119         code      TEXT     PRIMARY KEY,
13120         name      TEXT     NOT NULL
13121 );
13122
13123 CREATE TABLE acq.invoice (
13124     id             SERIAL      PRIMARY KEY,
13125     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
13126     provider       INT         NOT NULL REFERENCES acq.provider (id),
13127     shipper        INT         NOT NULL REFERENCES acq.provider (id),
13128     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13129     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
13130     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
13131     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
13132         payment_auth   TEXT,
13133         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
13134                                    DEFERRABLE INITIALLY DEFERRED,
13135         note           TEXT,
13136     complete       BOOL        NOT NULL DEFAULT FALSE,
13137     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13138 );
13139
13140 CREATE TABLE acq.invoice_entry (
13141     id              SERIAL      PRIMARY KEY,
13142     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13143     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13144     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13145     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13146     phys_item_count INT, -- and how many did staff count
13147     note            TEXT,
13148     billed_per_item BOOL,
13149     cost_billed     NUMERIC(8,2),
13150     actual_cost     NUMERIC(8,2),
13151         amount_paid     NUMERIC (8,2)
13152 );
13153
13154 CREATE TABLE acq.invoice_item_type (
13155     code    TEXT    PRIMARY KEY,
13156     name    TEXT    NOT NULL, -- i18n-ize
13157         prorate BOOL    NOT NULL DEFAULT FALSE
13158 );
13159
13160 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13161 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13162 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13163 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13164 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13165 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13166
13167 CREATE TABLE acq.po_item (
13168         id              SERIAL      PRIMARY KEY,
13169         purchase_order  INT         REFERENCES acq.purchase_order (id)
13170                                     ON UPDATE CASCADE ON DELETE SET NULL
13171                                     DEFERRABLE INITIALLY DEFERRED,
13172         fund_debit      INT         REFERENCES acq.fund_debit (id)
13173                                     DEFERRABLE INITIALLY DEFERRED,
13174         inv_item_type   TEXT        NOT NULL
13175                                     REFERENCES acq.invoice_item_type (code)
13176                                     DEFERRABLE INITIALLY DEFERRED,
13177         title           TEXT,
13178         author          TEXT,
13179         note            TEXT,
13180         estimated_cost  NUMERIC(8,2),
13181         fund            INT         REFERENCES acq.fund (id)
13182                                     DEFERRABLE INITIALLY DEFERRED,
13183         target          BIGINT
13184 );
13185
13186 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13187     id              SERIAL      PRIMARY KEY,
13188     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13189     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13190     fund_debit      INT         REFERENCES acq.fund_debit (id),
13191     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13192     title           TEXT,
13193     author          TEXT,
13194     note            TEXT,
13195     cost_billed     NUMERIC(8,2),
13196     actual_cost     NUMERIC(8,2),
13197     fund            INT         REFERENCES acq.fund (id)
13198                                 DEFERRABLE INITIALLY DEFERRED,
13199     amount_paid     NUMERIC (8,2),
13200     po_item         INT         REFERENCES acq.po_item (id)
13201                                 DEFERRABLE INITIALLY DEFERRED,
13202     target          BIGINT
13203 );
13204
13205 CREATE TABLE acq.edi_message (
13206     id               SERIAL          PRIMARY KEY,
13207     account          INTEGER         REFERENCES acq.edi_account(id)
13208                                      DEFERRABLE INITIALLY DEFERRED,
13209     remote_file      TEXT,
13210     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13211     translate_time   TIMESTAMPTZ,
13212     process_time     TIMESTAMPTZ,
13213     error_time       TIMESTAMPTZ,
13214     status           TEXT            NOT NULL DEFAULT 'new'
13215                                      CONSTRAINT status_value CHECK
13216                                      ( status IN (
13217                                         'new',          -- needs to be translated
13218                                         'translated',   -- needs to be processed
13219                                         'trans_error',  -- error in translation step
13220                                         'processed',    -- needs to have remote_file deleted
13221                                         'proc_error',   -- error in processing step
13222                                         'delete_error', -- error in deletion
13223                                         'retry',        -- need to retry
13224                                         'complete'      -- done
13225                                      )),
13226     edi              TEXT,
13227     jedi             TEXT,
13228     error            TEXT,
13229     purchase_order   INT             REFERENCES acq.purchase_order
13230                                      DEFERRABLE INITIALLY DEFERRED,
13231     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13232                                      CHECK ( message_type IN (
13233                                         'ORDERS',
13234                                         'ORDRSP',
13235                                         'INVOIC',
13236                                         'OSTENQ',
13237                                         'OSTRPT'
13238                                      ))
13239 );
13240
13241 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13242
13243 ALTER TABLE acq.provider_address
13244         ADD COLUMN fax_phone TEXT;
13245
13246 ALTER TABLE acq.provider_contact_address
13247         ADD COLUMN fax_phone TEXT;
13248
13249 CREATE TABLE acq.provider_note (
13250     id      SERIAL              PRIMARY KEY,
13251     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13252     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13253     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13254     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13255     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13256     value       TEXT            NOT NULL
13257 );
13258 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13259 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13260 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13261
13262 -- For each fund: the total allocation from all sources, in the
13263 -- currency of the fund (or 0 if there are no allocations)
13264
13265 CREATE VIEW acq.all_fund_allocation_total AS
13266 SELECT
13267     f.id AS fund,
13268     COALESCE( SUM( a.amount * acq.exchange_ratio(
13269         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13270     AS amount
13271 FROM
13272     acq.fund f
13273         LEFT JOIN acq.fund_allocation a
13274             ON a.fund = f.id
13275         LEFT JOIN acq.funding_source s
13276             ON a.funding_source = s.id
13277 GROUP BY
13278     f.id;
13279
13280 -- For every fund: the total encumbrances (or 0 if none),
13281 -- in the currency of the fund.
13282
13283 CREATE VIEW acq.all_fund_encumbrance_total AS
13284 SELECT
13285         f.id AS fund,
13286         COALESCE( encumb.amount, 0 ) AS amount
13287 FROM
13288         acq.fund AS f
13289                 LEFT JOIN (
13290                         SELECT
13291                                 fund,
13292                                 sum( amount ) AS amount
13293                         FROM
13294                                 acq.fund_debit
13295                         WHERE
13296                                 encumbrance
13297                         GROUP BY fund
13298                 ) AS encumb
13299                         ON f.id = encumb.fund;
13300
13301 -- For every fund: the total spent (or 0 if none),
13302 -- in the currency of the fund.
13303
13304 CREATE VIEW acq.all_fund_spent_total AS
13305 SELECT
13306     f.id AS fund,
13307     COALESCE( spent.amount, 0 ) AS amount
13308 FROM
13309     acq.fund AS f
13310         LEFT JOIN (
13311             SELECT
13312                 fund,
13313                 sum( amount ) AS amount
13314             FROM
13315                 acq.fund_debit
13316             WHERE
13317                 NOT encumbrance
13318             GROUP BY fund
13319         ) AS spent
13320             ON f.id = spent.fund;
13321
13322 -- For each fund: the amount not yet spent, in the currency
13323 -- of the fund.  May include encumbrances.
13324
13325 CREATE VIEW acq.all_fund_spent_balance AS
13326 SELECT
13327         c.fund,
13328         c.amount - d.amount AS amount
13329 FROM acq.all_fund_allocation_total c
13330     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13331
13332 -- For each fund: the amount neither spent nor encumbered,
13333 -- in the currency of the fund
13334
13335 CREATE VIEW acq.all_fund_combined_balance AS
13336 SELECT
13337      a.fund,
13338      a.amount - COALESCE( c.amount, 0 ) AS amount
13339 FROM
13340      acq.all_fund_allocation_total a
13341         LEFT OUTER JOIN (
13342             SELECT
13343                 fund,
13344                 SUM( amount ) AS amount
13345             FROM
13346                 acq.fund_debit
13347             GROUP BY
13348                 fund
13349         ) AS c USING ( fund );
13350
13351 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 $$
13352 DECLARE
13353         suffix TEXT;
13354         bucket_row RECORD;
13355         picklist_row RECORD;
13356         queue_row RECORD;
13357         folder_row RECORD;
13358 BEGIN
13359
13360     -- do some initial cleanup 
13361     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13362     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13363     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13364
13365     -- actor.*
13366     IF del_cards THEN
13367         DELETE FROM actor.card where usr = src_usr;
13368     ELSE
13369         IF deactivate_cards THEN
13370             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13371         END IF;
13372         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13373     END IF;
13374
13375
13376     IF del_addrs THEN
13377         DELETE FROM actor.usr_address WHERE usr = src_usr;
13378     ELSE
13379         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13380     END IF;
13381
13382     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13383     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13384     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13385     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13386     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13387
13388     -- permission.*
13389     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13390     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13391     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13392     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13393
13394
13395     -- container.*
13396         
13397         -- For each *_bucket table: transfer every bucket belonging to src_usr
13398         -- into the custody of dest_usr.
13399         --
13400         -- In order to avoid colliding with an existing bucket owned by
13401         -- the destination user, append the source user's id (in parenthesese)
13402         -- to the name.  If you still get a collision, add successive
13403         -- spaces to the name and keep trying until you succeed.
13404         --
13405         FOR bucket_row in
13406                 SELECT id, name
13407                 FROM   container.biblio_record_entry_bucket
13408                 WHERE  owner = src_usr
13409         LOOP
13410                 suffix := ' (' || src_usr || ')';
13411                 LOOP
13412                         BEGIN
13413                                 UPDATE  container.biblio_record_entry_bucket
13414                                 SET     owner = dest_usr, name = name || suffix
13415                                 WHERE   id = bucket_row.id;
13416                         EXCEPTION WHEN unique_violation THEN
13417                                 suffix := suffix || ' ';
13418                                 CONTINUE;
13419                         END;
13420                         EXIT;
13421                 END LOOP;
13422         END LOOP;
13423
13424         FOR bucket_row in
13425                 SELECT id, name
13426                 FROM   container.call_number_bucket
13427                 WHERE  owner = src_usr
13428         LOOP
13429                 suffix := ' (' || src_usr || ')';
13430                 LOOP
13431                         BEGIN
13432                                 UPDATE  container.call_number_bucket
13433                                 SET     owner = dest_usr, name = name || suffix
13434                                 WHERE   id = bucket_row.id;
13435                         EXCEPTION WHEN unique_violation THEN
13436                                 suffix := suffix || ' ';
13437                                 CONTINUE;
13438                         END;
13439                         EXIT;
13440                 END LOOP;
13441         END LOOP;
13442
13443         FOR bucket_row in
13444                 SELECT id, name
13445                 FROM   container.copy_bucket
13446                 WHERE  owner = src_usr
13447         LOOP
13448                 suffix := ' (' || src_usr || ')';
13449                 LOOP
13450                         BEGIN
13451                                 UPDATE  container.copy_bucket
13452                                 SET     owner = dest_usr, name = name || suffix
13453                                 WHERE   id = bucket_row.id;
13454                         EXCEPTION WHEN unique_violation THEN
13455                                 suffix := suffix || ' ';
13456                                 CONTINUE;
13457                         END;
13458                         EXIT;
13459                 END LOOP;
13460         END LOOP;
13461
13462         FOR bucket_row in
13463                 SELECT id, name
13464                 FROM   container.user_bucket
13465                 WHERE  owner = src_usr
13466         LOOP
13467                 suffix := ' (' || src_usr || ')';
13468                 LOOP
13469                         BEGIN
13470                                 UPDATE  container.user_bucket
13471                                 SET     owner = dest_usr, name = name || suffix
13472                                 WHERE   id = bucket_row.id;
13473                         EXCEPTION WHEN unique_violation THEN
13474                                 suffix := suffix || ' ';
13475                                 CONTINUE;
13476                         END;
13477                         EXIT;
13478                 END LOOP;
13479         END LOOP;
13480
13481         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13482
13483     -- vandelay.*
13484         -- transfer queues the same way we transfer buckets (see above)
13485         FOR queue_row in
13486                 SELECT id, name
13487                 FROM   vandelay.queue
13488                 WHERE  owner = src_usr
13489         LOOP
13490                 suffix := ' (' || src_usr || ')';
13491                 LOOP
13492                         BEGIN
13493                                 UPDATE  vandelay.queue
13494                                 SET     owner = dest_usr, name = name || suffix
13495                                 WHERE   id = queue_row.id;
13496                         EXCEPTION WHEN unique_violation THEN
13497                                 suffix := suffix || ' ';
13498                                 CONTINUE;
13499                         END;
13500                         EXIT;
13501                 END LOOP;
13502         END LOOP;
13503
13504     -- money.*
13505     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13506     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13507     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13508     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13509     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13510
13511     -- action.*
13512     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13513     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13514     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13515
13516     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13517     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13518     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13519     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13520
13521     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13522     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13523     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13524     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13525     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13526
13527     -- acq.*
13528     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13529         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13530
13531         -- transfer picklists the same way we transfer buckets (see above)
13532         FOR picklist_row in
13533                 SELECT id, name
13534                 FROM   acq.picklist
13535                 WHERE  owner = src_usr
13536         LOOP
13537                 suffix := ' (' || src_usr || ')';
13538                 LOOP
13539                         BEGIN
13540                                 UPDATE  acq.picklist
13541                                 SET     owner = dest_usr, name = name || suffix
13542                                 WHERE   id = picklist_row.id;
13543                         EXCEPTION WHEN unique_violation THEN
13544                                 suffix := suffix || ' ';
13545                                 CONTINUE;
13546                         END;
13547                         EXIT;
13548                 END LOOP;
13549         END LOOP;
13550
13551     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13552     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13553     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13554     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13555     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13556     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13557     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13558     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13559
13560     -- asset.*
13561     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13562     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13563     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13564     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13565     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13566     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13567
13568     -- serial.*
13569     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13570     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13571
13572     -- reporter.*
13573     -- It's not uncommon to define the reporter schema in a replica 
13574     -- DB only, so don't assume these tables exist in the write DB.
13575     BEGIN
13576         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13577     EXCEPTION WHEN undefined_table THEN
13578         -- do nothing
13579     END;
13580     BEGIN
13581         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13582     EXCEPTION WHEN undefined_table THEN
13583         -- do nothing
13584     END;
13585     BEGIN
13586         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13587     EXCEPTION WHEN undefined_table THEN
13588         -- do nothing
13589     END;
13590     BEGIN
13591                 -- transfer folders the same way we transfer buckets (see above)
13592                 FOR folder_row in
13593                         SELECT id, name
13594                         FROM   reporter.template_folder
13595                         WHERE  owner = src_usr
13596                 LOOP
13597                         suffix := ' (' || src_usr || ')';
13598                         LOOP
13599                                 BEGIN
13600                                         UPDATE  reporter.template_folder
13601                                         SET     owner = dest_usr, name = name || suffix
13602                                         WHERE   id = folder_row.id;
13603                                 EXCEPTION WHEN unique_violation THEN
13604                                         suffix := suffix || ' ';
13605                                         CONTINUE;
13606                                 END;
13607                                 EXIT;
13608                         END LOOP;
13609                 END LOOP;
13610     EXCEPTION WHEN undefined_table THEN
13611         -- do nothing
13612     END;
13613     BEGIN
13614                 -- transfer folders the same way we transfer buckets (see above)
13615                 FOR folder_row in
13616                         SELECT id, name
13617                         FROM   reporter.report_folder
13618                         WHERE  owner = src_usr
13619                 LOOP
13620                         suffix := ' (' || src_usr || ')';
13621                         LOOP
13622                                 BEGIN
13623                                         UPDATE  reporter.report_folder
13624                                         SET     owner = dest_usr, name = name || suffix
13625                                         WHERE   id = folder_row.id;
13626                                 EXCEPTION WHEN unique_violation THEN
13627                                         suffix := suffix || ' ';
13628                                         CONTINUE;
13629                                 END;
13630                                 EXIT;
13631                         END LOOP;
13632                 END LOOP;
13633     EXCEPTION WHEN undefined_table THEN
13634         -- do nothing
13635     END;
13636     BEGIN
13637                 -- transfer folders the same way we transfer buckets (see above)
13638                 FOR folder_row in
13639                         SELECT id, name
13640                         FROM   reporter.output_folder
13641                         WHERE  owner = src_usr
13642                 LOOP
13643                         suffix := ' (' || src_usr || ')';
13644                         LOOP
13645                                 BEGIN
13646                                         UPDATE  reporter.output_folder
13647                                         SET     owner = dest_usr, name = name || suffix
13648                                         WHERE   id = folder_row.id;
13649                                 EXCEPTION WHEN unique_violation THEN
13650                                         suffix := suffix || ' ';
13651                                         CONTINUE;
13652                                 END;
13653                                 EXIT;
13654                         END LOOP;
13655                 END LOOP;
13656     EXCEPTION WHEN undefined_table THEN
13657         -- do nothing
13658     END;
13659
13660     -- Finally, delete the source user
13661     DELETE FROM actor.usr WHERE id = src_usr;
13662
13663 END;
13664 $$ LANGUAGE plpgsql;
13665
13666 -- The "add" trigger functions should protect against existing NULLed values, just in case
13667 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13668 BEGIN
13669     IF NOT NEW.voided THEN
13670         UPDATE  money.materialized_billable_xact_summary
13671           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13672             last_billing_ts = NEW.billing_ts,
13673             last_billing_note = NEW.note,
13674             last_billing_type = NEW.billing_type,
13675             balance_owed = balance_owed + NEW.amount
13676           WHERE id = NEW.xact;
13677     END IF;
13678
13679     RETURN NEW;
13680 END;
13681 $$ LANGUAGE PLPGSQL;
13682
13683 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13684 BEGIN
13685     IF NOT NEW.voided THEN
13686         UPDATE  money.materialized_billable_xact_summary
13687           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13688             last_payment_ts = NEW.payment_ts,
13689             last_payment_note = NEW.note,
13690             last_payment_type = TG_ARGV[0],
13691             balance_owed = balance_owed - NEW.amount
13692           WHERE id = NEW.xact;
13693     END IF;
13694
13695     RETURN NEW;
13696 END;
13697 $$ LANGUAGE PLPGSQL;
13698
13699 -- Refresh the mat view with the corrected underlying view
13700 TRUNCATE money.materialized_billable_xact_summary;
13701 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13702
13703 -- Now redefine the view as a window onto the materialized view
13704 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13705     SELECT * FROM money.materialized_billable_xact_summary;
13706
13707 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13708     user_id    IN INTEGER,
13709     perm_code  IN TEXT
13710 )
13711 RETURNS SETOF INTEGER AS $$
13712 --
13713 -- Return a set of all the org units for which a given user has a given
13714 -- permission, granted directly (not through inheritance from a parent
13715 -- org unit).
13716 --
13717 -- The permissions apply to a minimum depth of the org unit hierarchy,
13718 -- for the org unit(s) to which the user is assigned.  (They also apply
13719 -- to the subordinates of those org units, but we don't report the
13720 -- subordinates here.)
13721 --
13722 -- For purposes of this function, the permission.usr_work_ou_map table
13723 -- defines which users belong to which org units.  I.e. we ignore the
13724 -- home_ou column of actor.usr.
13725 --
13726 -- The result set may contain duplicates, which should be eliminated
13727 -- by a DISTINCT clause.
13728 --
13729 DECLARE
13730     b_super       BOOLEAN;
13731     n_perm        INTEGER;
13732     n_min_depth   INTEGER;
13733     n_work_ou     INTEGER;
13734     n_curr_ou     INTEGER;
13735     n_depth       INTEGER;
13736     n_curr_depth  INTEGER;
13737 BEGIN
13738     --
13739     -- Check for superuser
13740     --
13741     SELECT INTO b_super
13742         super_user
13743     FROM
13744         actor.usr
13745     WHERE
13746         id = user_id;
13747     --
13748     IF NOT FOUND THEN
13749         return;             -- No user?  No permissions.
13750     ELSIF b_super THEN
13751         --
13752         -- Super user has all permissions everywhere
13753         --
13754         FOR n_work_ou IN
13755             SELECT
13756                 id
13757             FROM
13758                 actor.org_unit
13759             WHERE
13760                 parent_ou IS NULL
13761         LOOP
13762             RETURN NEXT n_work_ou;
13763         END LOOP;
13764         RETURN;
13765     END IF;
13766     --
13767     -- Translate the permission name
13768     -- to a numeric permission id
13769     --
13770     SELECT INTO n_perm
13771         id
13772     FROM
13773         permission.perm_list
13774     WHERE
13775         code = perm_code;
13776     --
13777     IF NOT FOUND THEN
13778         RETURN;               -- No such permission
13779     END IF;
13780     --
13781     -- Find the highest-level org unit (i.e. the minimum depth)
13782     -- to which the permission is applied for this user
13783     --
13784     -- This query is modified from the one in permission.usr_perms().
13785     --
13786     SELECT INTO n_min_depth
13787         min( depth )
13788     FROM    (
13789         SELECT depth
13790           FROM permission.usr_perm_map upm
13791          WHERE upm.usr = user_id
13792            AND (upm.perm = n_perm OR upm.perm = -1)
13793                     UNION
13794         SELECT  gpm.depth
13795           FROM  permission.grp_perm_map gpm
13796           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13797             AND gpm.grp IN (
13798                SELECT   (permission.grp_ancestors(
13799                     (SELECT profile FROM actor.usr WHERE id = user_id)
13800                 )).id
13801             )
13802                     UNION
13803         SELECT  p.depth
13804           FROM  permission.grp_perm_map p
13805           WHERE (p.perm = n_perm OR p.perm = -1)
13806             AND p.grp IN (
13807                 SELECT (permission.grp_ancestors(m.grp)).id
13808                 FROM   permission.usr_grp_map m
13809                 WHERE  m.usr = user_id
13810             )
13811     ) AS x;
13812     --
13813     IF NOT FOUND THEN
13814         RETURN;                -- No such permission for this user
13815     END IF;
13816     --
13817     -- Identify the org units to which the user is assigned.  Note that
13818     -- we pay no attention to the home_ou column in actor.usr.
13819     --
13820     FOR n_work_ou IN
13821         SELECT
13822             work_ou
13823         FROM
13824             permission.usr_work_ou_map
13825         WHERE
13826             usr = user_id
13827     LOOP            -- For each org unit to which the user is assigned
13828         --
13829         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13830         -- We take it on faith that this depth agrees with the actual hierarchy
13831         -- defined in actor.org_unit.
13832         --
13833         SELECT INTO n_depth
13834             type.depth
13835         FROM
13836             actor.org_unit_type type
13837                 INNER JOIN actor.org_unit ou
13838                     ON ( ou.ou_type = type.id )
13839         WHERE
13840             ou.id = n_work_ou;
13841         --
13842         IF NOT FOUND THEN
13843             CONTINUE;        -- Maybe raise exception?
13844         END IF;
13845         --
13846         -- Compare the depth of the work org unit to the
13847         -- minimum depth, and branch accordingly
13848         --
13849         IF n_depth = n_min_depth THEN
13850             --
13851             -- The org unit is at the right depth, so return it.
13852             --
13853             RETURN NEXT n_work_ou;
13854         ELSIF n_depth > n_min_depth THEN
13855             --
13856             -- Traverse the org unit tree toward the root,
13857             -- until you reach the minimum depth determined above
13858             --
13859             n_curr_depth := n_depth;
13860             n_curr_ou := n_work_ou;
13861             WHILE n_curr_depth > n_min_depth LOOP
13862                 SELECT INTO n_curr_ou
13863                     parent_ou
13864                 FROM
13865                     actor.org_unit
13866                 WHERE
13867                     id = n_curr_ou;
13868                 --
13869                 IF FOUND THEN
13870                     n_curr_depth := n_curr_depth - 1;
13871                 ELSE
13872                     --
13873                     -- This can happen only if the hierarchy defined in
13874                     -- actor.org_unit is corrupted, or out of sync with
13875                     -- the depths defined in actor.org_unit_type.
13876                     -- Maybe we should raise an exception here, instead
13877                     -- of silently ignoring the problem.
13878                     --
13879                     n_curr_ou = NULL;
13880                     EXIT;
13881                 END IF;
13882             END LOOP;
13883             --
13884             IF n_curr_ou IS NOT NULL THEN
13885                 RETURN NEXT n_curr_ou;
13886             END IF;
13887         ELSE
13888             --
13889             -- The permission applies only at a depth greater than the work org unit.
13890             -- Use connectby() to find all dependent org units at the specified depth.
13891             --
13892             FOR n_curr_ou IN
13893                 SELECT ou::INTEGER
13894                 FROM connectby(
13895                         'actor.org_unit',         -- table name
13896                         'id',                     -- key column
13897                         'parent_ou',              -- recursive foreign key
13898                         n_work_ou::TEXT,          -- id of starting point
13899                         (n_min_depth - n_depth)   -- max depth to search, relative
13900                     )                             --   to starting point
13901                     AS t(
13902                         ou text,            -- dependent org unit
13903                         parent_ou text,     -- (ignore)
13904                         level int           -- depth relative to starting point
13905                     )
13906                 WHERE
13907                     level = n_min_depth - n_depth
13908             LOOP
13909                 RETURN NEXT n_curr_ou;
13910             END LOOP;
13911         END IF;
13912         --
13913     END LOOP;
13914     --
13915     RETURN;
13916     --
13917 END;
13918 $$ LANGUAGE 'plpgsql';
13919
13920 ALTER TABLE acq.purchase_order
13921         ADD COLUMN cancel_reason INT
13922                 REFERENCES acq.cancel_reason( id )
13923             DEFERRABLE INITIALLY DEFERRED,
13924         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
13925
13926 -- Build the history table and lifecycle view
13927 -- for acq.purchase_order
13928
13929 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
13930
13931 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
13932
13933 ALTER TABLE acq.lineitem
13934         ADD COLUMN cancel_reason INT
13935                 REFERENCES acq.cancel_reason( id )
13936             DEFERRABLE INITIALLY DEFERRED,
13937         ADD COLUMN estimated_unit_price NUMERIC,
13938         ADD COLUMN claim_policy INT
13939                 REFERENCES acq.claim_policy
13940                 DEFERRABLE INITIALLY DEFERRED,
13941         ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
13942
13943 -- Build the history table and lifecycle view
13944 -- for acq.lineitem
13945
13946 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
13947 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
13948
13949 ALTER TABLE acq.lineitem_detail
13950         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
13951                                             DEFERRABLE INITIALLY DEFERRED;
13952
13953 ALTER TABLE acq.lineitem_detail
13954         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
13955
13956 ALTER TABLE acq.lineitem_detail
13957         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13958                 ON DELETE CASCADE
13959                 DEFERRABLE INITIALLY DEFERRED;
13960
13961 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
13962
13963 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
13964         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
13965
13966 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
13967         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
13968
13969 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
13970
13971     use MARC::Record;
13972     use MARC::File::XML (BinaryEncoding => 'UTF-8');
13973     use strict;
13974
13975     my $target_xml = shift;
13976     my $source_xml = shift;
13977     my $field_spec = shift;
13978
13979     my $target_r = MARC::Record->new_from_xml( $target_xml );
13980     my $source_r = MARC::Record->new_from_xml( $source_xml );
13981
13982     return $target_xml unless ($target_r && $source_r);
13983
13984     my @field_list = split(',', $field_spec);
13985
13986     my %fields;
13987     for my $f (@field_list) {
13988         $f =~ s/^\s*//; $f =~ s/\s*$//;
13989         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
13990             my $field = $1;
13991             $field =~ s/\s+//;
13992             my $sf = $2;
13993             $sf =~ s/\s+//;
13994             my $match = $3;
13995             $match =~ s/^\s*//; $match =~ s/\s*$//;
13996             $fields{$field} = { sf => [ split('', $sf) ] };
13997             if ($match) {
13998                 my ($msf,$mre) = split('~', $match);
13999                 if (length($msf) > 0 and length($mre) > 0) {
14000                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14001                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14002                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14003                 }
14004             }
14005         }
14006     }
14007
14008     for my $f ( keys %fields) {
14009         if ( @{$fields{$f}{sf}} ) {
14010             for my $from_field ($source_r->field( $f )) {
14011                 my @tos = $target_r->field( $f );
14012                 if (!@tos) {
14013                     next if (exists($fields{$f}{match}));
14014                     my @new_fields = map { $_->clone } $source_r->field( $f );
14015                     $target_r->insert_fields_ordered( @new_fields );
14016                 } else {
14017                     for my $to_field (@tos) {
14018                         if (exists($fields{$f}{match})) {
14019                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14020                         }
14021                         my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
14022                         $to_field->add_subfields( @new_sf );
14023                     }
14024                 }
14025             }
14026         } else {
14027             my @new_fields = map { $_->clone } $source_r->field( $f );
14028             $target_r->insert_fields_ordered( @new_fields );
14029         }
14030     }
14031
14032     $target_xml = $target_r->as_xml_record;
14033     $target_xml =~ s/^<\?.+?\?>$//mo;
14034     $target_xml =~ s/\n//sgo;
14035     $target_xml =~ s/>\s+</></sgo;
14036
14037     return $target_xml;
14038
14039 $_$ LANGUAGE PLPERLU;
14040
14041 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14042
14043     use MARC::Record;
14044     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14045     use strict;
14046
14047     my $xml = shift;
14048     my $r = MARC::Record->new_from_xml( $xml );
14049
14050     return $xml unless ($r);
14051
14052     my $field_spec = shift;
14053     my @field_list = split(',', $field_spec);
14054
14055     my %fields;
14056     for my $f (@field_list) {
14057         $f =~ s/^\s*//; $f =~ s/\s*$//;
14058         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14059             my $field = $1;
14060             $field =~ s/\s+//;
14061             my $sf = $2;
14062             $sf =~ s/\s+//;
14063             my $match = $3;
14064             $match =~ s/^\s*//; $match =~ s/\s*$//;
14065             $fields{$field} = { sf => [ split('', $sf) ] };
14066             if ($match) {
14067                 my ($msf,$mre) = split('~', $match);
14068                 if (length($msf) > 0 and length($mre) > 0) {
14069                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14070                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14071                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14072                 }
14073             }
14074         }
14075     }
14076
14077     for my $f ( keys %fields) {
14078         for my $to_field ($r->field( $f )) {
14079             if (exists($fields{$f}{match})) {
14080                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14081             }
14082
14083             if ( @{$fields{$f}{sf}} ) {
14084                 $to_field->delete_subfield(code => $fields{$f}{sf});
14085             } else {
14086                 $r->delete_field( $to_field );
14087             }
14088         }
14089     }
14090
14091     $xml = $r->as_xml_record;
14092     $xml =~ s/^<\?.+?\?>$//mo;
14093     $xml =~ s/\n//sgo;
14094     $xml =~ s/>\s+</></sgo;
14095
14096     return $xml;
14097
14098 $_$ LANGUAGE PLPERLU;
14099
14100 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14101     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
14102 $_$ LANGUAGE SQL;
14103
14104 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14105     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
14106 $_$ LANGUAGE SQL;
14107
14108 CREATE VIEW action.unfulfilled_hold_max_loop AS
14109         SELECT  hold,
14110                 max(count) AS max
14111         FROM    action.unfulfilled_hold_loops
14112         GROUP BY 1;
14113
14114 ALTER TABLE acq.lineitem_attr
14115         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
14116
14117 ALTER TABLE acq.lineitem_attr
14118         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14119                 ON DELETE CASCADE
14120                 DEFERRABLE INITIALLY DEFERRED;
14121
14122 ALTER TABLE acq.po_note
14123         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
14124
14125 CREATE TABLE vandelay.merge_profile (
14126     id              BIGSERIAL   PRIMARY KEY,
14127     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
14128     name            TEXT        NOT NULL,
14129     add_spec        TEXT,
14130     replace_spec    TEXT,
14131     strip_spec      TEXT,
14132     preserve_spec   TEXT,
14133     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14134     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))
14135 );
14136
14137 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14138 DECLARE
14139     attr        RECORD;
14140     attr_def    RECORD;
14141     eg_rec      RECORD;
14142     id_value    TEXT;
14143     exact_id    BIGINT;
14144 BEGIN
14145
14146     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14147
14148     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14149
14150     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14151         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14152
14153         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14154             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14155             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14156             IF exact_id IS NOT NULL THEN
14157                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14158             END IF;
14159         END IF;
14160     END IF;
14161
14162     IF exact_id IS NULL THEN
14163         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
14164
14165             -- All numbers? check for an id match
14166             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14167                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14168                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14169                 END LOOP;
14170             END IF;
14171
14172             -- Looks like an ISBN? check for an isbn match
14173             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14174                 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
14175                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14176                     IF FOUND THEN
14177                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14178                     END IF;
14179                 END LOOP;
14180
14181                 -- subcheck for isbn-as-tcn
14182                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14183                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14184                 END LOOP;
14185             END IF;
14186
14187             -- check for an OCLC tcn_value match
14188             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14189                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14190                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14191                 END LOOP;
14192             END IF;
14193
14194             -- check for a direct tcn_value match
14195             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14196                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14197             END LOOP;
14198
14199             -- check for a direct item barcode match
14200             FOR eg_rec IN
14201                     SELECT  DISTINCT b.*
14202                       FROM  biblio.record_entry b
14203                             JOIN asset.call_number cn ON (cn.record = b.id)
14204                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14205                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14206             LOOP
14207                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14208             END LOOP;
14209
14210         END LOOP;
14211     END IF;
14212
14213     RETURN NULL;
14214 END;
14215 $func$ LANGUAGE PLPGSQL;
14216
14217 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 $_$
14218     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14219 $_$ LANGUAGE SQL;
14220
14221 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14222 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14223 DECLARE
14224     output              vandelay.compile_profile%ROWTYPE;
14225     profile             vandelay.merge_profile%ROWTYPE;
14226     profile_tmpl        TEXT;
14227     profile_tmpl_owner  TEXT;
14228     add_rule            TEXT := '';
14229     strip_rule          TEXT := '';
14230     replace_rule        TEXT := '';
14231     preserve_rule       TEXT := '';
14232
14233 BEGIN
14234
14235     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14236     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14237
14238     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14239         SELECT  p.* INTO profile
14240           FROM  vandelay.merge_profile p
14241                 JOIN actor.org_unit u ON (u.id = p.owner)
14242           WHERE p.name = profile_tmpl
14243                 AND u.shortname = profile_tmpl_owner;
14244
14245         IF profile.id IS NOT NULL THEN
14246             add_rule := COALESCE(profile.add_spec,'');
14247             strip_rule := COALESCE(profile.strip_spec,'');
14248             replace_rule := COALESCE(profile.replace_spec,'');
14249             preserve_rule := COALESCE(profile.preserve_spec,'');
14250         END IF;
14251     END IF;
14252
14253     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
14254     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
14255     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
14256     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
14257
14258     output.add_rule := BTRIM(add_rule,',');
14259     output.replace_rule := BTRIM(replace_rule,',');
14260     output.strip_rule := BTRIM(strip_rule,',');
14261     output.preserve_rule := BTRIM(preserve_rule,',');
14262
14263     RETURN output;
14264 END;
14265 $_$ LANGUAGE PLPGSQL;
14266
14267 -- Template-based marc munging functions
14268 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14269 DECLARE
14270     merge_profile   vandelay.merge_profile%ROWTYPE;
14271     dyn_profile     vandelay.compile_profile%ROWTYPE;
14272     editor_string   TEXT;
14273     editor_id       INT;
14274     source_marc     TEXT;
14275     target_marc     TEXT;
14276     eg_marc         TEXT;
14277     replace_rule    TEXT;
14278     match_count     INT;
14279 BEGIN
14280
14281     SELECT  b.marc INTO eg_marc
14282       FROM  biblio.record_entry b
14283       WHERE b.id = eg_id
14284       LIMIT 1;
14285
14286     IF eg_marc IS NULL OR v_marc IS NULL THEN
14287         -- RAISE NOTICE 'no marc for template or bib record';
14288         RETURN FALSE;
14289     END IF;
14290
14291     dyn_profile := vandelay.compile_profile( v_marc );
14292
14293     IF merge_profile_id IS NOT NULL THEN
14294         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14295         IF FOUND THEN
14296             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14297             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14298             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14299             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14300         END IF;
14301     END IF;
14302
14303     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14304         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14305         RETURN FALSE;
14306     END IF;
14307
14308     IF dyn_profile.replace_rule <> '' THEN
14309         source_marc = v_marc;
14310         target_marc = eg_marc;
14311         replace_rule = dyn_profile.replace_rule;
14312     ELSE
14313         source_marc = eg_marc;
14314         target_marc = v_marc;
14315         replace_rule = dyn_profile.preserve_rule;
14316     END IF;
14317
14318     UPDATE  biblio.record_entry
14319       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14320       WHERE id = eg_id;
14321
14322     IF NOT FOUND THEN
14323         -- RAISE NOTICE 'update of biblio.record_entry failed';
14324         RETURN FALSE;
14325     END IF;
14326
14327     RETURN TRUE;
14328
14329 END;
14330 $$ LANGUAGE PLPGSQL;
14331
14332 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14333     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14334 $$ LANGUAGE SQL;
14335
14336 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14337 DECLARE
14338     merge_profile   vandelay.merge_profile%ROWTYPE;
14339     dyn_profile     vandelay.compile_profile%ROWTYPE;
14340     editor_string   TEXT;
14341     editor_id       INT;
14342     source_marc     TEXT;
14343     target_marc     TEXT;
14344     eg_marc         TEXT;
14345     v_marc          TEXT;
14346     replace_rule    TEXT;
14347     match_count     INT;
14348 BEGIN
14349
14350     SELECT  q.marc INTO v_marc
14351       FROM  vandelay.queued_record q
14352             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14353       LIMIT 1;
14354
14355     IF v_marc IS NULL THEN
14356         -- RAISE NOTICE 'no marc for vandelay or bib record';
14357         RETURN FALSE;
14358     END IF;
14359
14360     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14361         UPDATE  vandelay.queued_bib_record
14362           SET   imported_as = eg_id,
14363                 import_time = NOW()
14364           WHERE id = import_id;
14365
14366         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14367
14368         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14369             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14370
14371             IF editor_id IS NULL THEN
14372                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14373             END IF;
14374
14375             IF editor_id IS NOT NULL THEN
14376                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14377             END IF;
14378         END IF;
14379
14380         RETURN TRUE;
14381     END IF;
14382
14383     -- RAISE NOTICE 'update of biblio.record_entry failed';
14384
14385     RETURN FALSE;
14386
14387 END;
14388 $$ LANGUAGE PLPGSQL;
14389
14390 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14391 DECLARE
14392     eg_id           BIGINT;
14393     match_count     INT;
14394     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14395 BEGIN
14396
14397     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14398
14399     IF FOUND THEN
14400         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14401         RETURN FALSE;
14402     END IF;
14403
14404     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14405
14406     IF match_count <> 1 THEN
14407         -- RAISE NOTICE 'not an exact match';
14408         RETURN FALSE;
14409     END IF;
14410
14411     SELECT  d.* INTO match_attr
14412       FROM  vandelay.bib_attr_definition d
14413             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14414             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14415       WHERE m.queued_record = import_id;
14416
14417     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14418         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14419         RETURN FALSE;
14420     END IF;
14421
14422     SELECT  m.eg_record INTO eg_id
14423       FROM  vandelay.bib_match m
14424       WHERE m.queued_record = import_id
14425       LIMIT 1;
14426
14427     IF eg_id IS NULL THEN
14428         RETURN FALSE;
14429     END IF;
14430
14431     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14432 END;
14433 $$ LANGUAGE PLPGSQL;
14434
14435 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14436 DECLARE
14437     queued_record   vandelay.queued_bib_record%ROWTYPE;
14438 BEGIN
14439
14440     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14441
14442         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14443             RETURN NEXT queued_record.id;
14444         END IF;
14445
14446     END LOOP;
14447
14448     RETURN;
14449
14450 END;
14451 $$ LANGUAGE PLPGSQL;
14452
14453 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14454     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14455 $$ LANGUAGE SQL;
14456
14457 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14458 DECLARE
14459     merge_profile   vandelay.merge_profile%ROWTYPE;
14460     dyn_profile     vandelay.compile_profile%ROWTYPE;
14461     source_marc     TEXT;
14462     target_marc     TEXT;
14463     eg_marc         TEXT;
14464     v_marc          TEXT;
14465     replace_rule    TEXT;
14466     match_count     INT;
14467 BEGIN
14468
14469     SELECT  b.marc INTO eg_marc
14470       FROM  authority.record_entry b
14471             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14472       LIMIT 1;
14473
14474     SELECT  q.marc INTO v_marc
14475       FROM  vandelay.queued_record q
14476             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14477       LIMIT 1;
14478
14479     IF eg_marc IS NULL OR v_marc IS NULL THEN
14480         -- RAISE NOTICE 'no marc for vandelay or authority record';
14481         RETURN FALSE;
14482     END IF;
14483
14484     dyn_profile := vandelay.compile_profile( v_marc );
14485
14486     IF merge_profile_id IS NOT NULL THEN
14487         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14488         IF FOUND THEN
14489             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14490             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14491             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14492             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14493         END IF;
14494     END IF;
14495
14496     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14497         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14498         RETURN FALSE;
14499     END IF;
14500
14501     IF dyn_profile.replace_rule <> '' THEN
14502         source_marc = v_marc;
14503         target_marc = eg_marc;
14504         replace_rule = dyn_profile.replace_rule;
14505     ELSE
14506         source_marc = eg_marc;
14507         target_marc = v_marc;
14508         replace_rule = dyn_profile.preserve_rule;
14509     END IF;
14510
14511     UPDATE  authority.record_entry
14512       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14513       WHERE id = eg_id;
14514
14515     IF FOUND THEN
14516         UPDATE  vandelay.queued_authority_record
14517           SET   imported_as = eg_id,
14518                 import_time = NOW()
14519           WHERE id = import_id;
14520         RETURN TRUE;
14521     END IF;
14522
14523     -- RAISE NOTICE 'update of authority.record_entry failed';
14524
14525     RETURN FALSE;
14526
14527 END;
14528 $$ LANGUAGE PLPGSQL;
14529
14530 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14531 DECLARE
14532     eg_id           BIGINT;
14533     match_count     INT;
14534 BEGIN
14535     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14536
14537     IF match_count <> 1 THEN
14538         -- RAISE NOTICE 'not an exact match';
14539         RETURN FALSE;
14540     END IF;
14541
14542     SELECT  m.eg_record INTO eg_id
14543       FROM  vandelay.authority_match m
14544       WHERE m.queued_record = import_id
14545       LIMIT 1;
14546
14547     IF eg_id IS NULL THEN
14548         RETURN FALSE;
14549     END IF;
14550
14551     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14552 END;
14553 $$ LANGUAGE PLPGSQL;
14554
14555 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14556 DECLARE
14557     queued_record   vandelay.queued_authority_record%ROWTYPE;
14558 BEGIN
14559
14560     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14561
14562         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14563             RETURN NEXT queued_record.id;
14564         END IF;
14565
14566     END LOOP;
14567
14568     RETURN;
14569
14570 END;
14571 $$ LANGUAGE PLPGSQL;
14572
14573 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14574     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14575 $$ LANGUAGE SQL;
14576
14577 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14578 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14579 DECLARE
14580     eg_tcn          TEXT;
14581     eg_tcn_source   TEXT;
14582     output          vandelay.tcn_data%ROWTYPE;
14583 BEGIN
14584
14585     -- 001/003
14586     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14587     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14588
14589         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14590         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14591             eg_tcn_source := 'System Local';
14592         END IF;
14593
14594         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14595
14596         IF NOT FOUND THEN
14597             output.used := FALSE;
14598         ELSE
14599             output.used := TRUE;
14600         END IF;
14601
14602         output.tcn := eg_tcn;
14603         output.tcn_source := eg_tcn_source;
14604         RETURN NEXT output;
14605
14606     END IF;
14607
14608     -- 901 ab
14609     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14610     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14611
14612         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14613         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14614             eg_tcn_source := 'System Local';
14615         END IF;
14616
14617         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14618
14619         IF NOT FOUND THEN
14620             output.used := FALSE;
14621         ELSE
14622             output.used := TRUE;
14623         END IF;
14624
14625         output.tcn := eg_tcn;
14626         output.tcn_source := eg_tcn_source;
14627         RETURN NEXT output;
14628
14629     END IF;
14630
14631     -- 039 ab
14632     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14633     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14634
14635         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14636         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14637             eg_tcn_source := 'System Local';
14638         END IF;
14639
14640         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14641
14642         IF NOT FOUND THEN
14643             output.used := FALSE;
14644         ELSE
14645             output.used := TRUE;
14646         END IF;
14647
14648         output.tcn := eg_tcn;
14649         output.tcn_source := eg_tcn_source;
14650         RETURN NEXT output;
14651
14652     END IF;
14653
14654     -- 020 a
14655     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14656     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14657
14658         eg_tcn_source := 'ISBN';
14659
14660         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14661
14662         IF NOT FOUND THEN
14663             output.used := FALSE;
14664         ELSE
14665             output.used := TRUE;
14666         END IF;
14667
14668         output.tcn := eg_tcn;
14669         output.tcn_source := eg_tcn_source;
14670         RETURN NEXT output;
14671
14672     END IF;
14673
14674     -- 022 a
14675     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14676     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14677
14678         eg_tcn_source := 'ISSN';
14679
14680         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14681
14682         IF NOT FOUND THEN
14683             output.used := FALSE;
14684         ELSE
14685             output.used := TRUE;
14686         END IF;
14687
14688         output.tcn := eg_tcn;
14689         output.tcn_source := eg_tcn_source;
14690         RETURN NEXT output;
14691
14692     END IF;
14693
14694     -- 010 a
14695     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14696     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14697
14698         eg_tcn_source := 'LCCN';
14699
14700         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14701
14702         IF NOT FOUND THEN
14703             output.used := FALSE;
14704         ELSE
14705             output.used := TRUE;
14706         END IF;
14707
14708         output.tcn := eg_tcn;
14709         output.tcn_source := eg_tcn_source;
14710         RETURN NEXT output;
14711
14712     END IF;
14713
14714     -- 035 a
14715     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
14716     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14717
14718         eg_tcn_source := 'System Legacy';
14719
14720         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14721
14722         IF NOT FOUND THEN
14723             output.used := FALSE;
14724         ELSE
14725             output.used := TRUE;
14726         END IF;
14727
14728         output.tcn := eg_tcn;
14729         output.tcn_source := eg_tcn_source;
14730         RETURN NEXT output;
14731
14732     END IF;
14733
14734     RETURN;
14735 END;
14736 $_$ LANGUAGE PLPGSQL;
14737
14738 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14739
14740 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);
14741
14742 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
14743
14744 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14745 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14746 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14747 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14748 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14749
14750 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14751 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14752 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14753 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14754
14755 ALTER TABLE metabib.series_field_entry
14756         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14757                 REFERENCES biblio.record_entry (id)
14758                 ON DELETE CASCADE
14759                 DEFERRABLE INITIALLY DEFERRED;
14760
14761 ALTER TABLE metabib.series_field_entry
14762         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14763                 REFERENCES config.metabib_field (id)
14764                 ON DELETE CASCADE
14765                 DEFERRABLE INITIALLY DEFERRED;
14766
14767 CREATE TABLE acq.claim_policy_action (
14768         id              SERIAL       PRIMARY KEY,
14769         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14770                                  ON DELETE CASCADE
14771                                      DEFERRABLE INITIALLY DEFERRED,
14772         action_interval INTERVAL     NOT NULL,
14773         action          INT          NOT NULL REFERENCES acq.claim_event_type
14774                                      DEFERRABLE INITIALLY DEFERRED,
14775         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14776 );
14777
14778 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14779 DECLARE
14780         value           TEXT;
14781         atype           TEXT;
14782         prov            INT;
14783         pos             INT;
14784         adef            RECORD;
14785         xpath_string    TEXT;
14786 BEGIN
14787         FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14788
14789                 SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14790
14791                 IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14792                         IF (atype = 'lineitem_provider_attr_definition') THEN
14793                                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14794                                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14795                         END IF;
14796                         
14797                         IF (atype = 'lineitem_provider_attr_definition') THEN
14798                                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14799                         ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14800                                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14801                         ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14802                                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14803                         END IF;
14804
14805             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14806
14807             IF (adef.code = 'title' OR adef.code = 'author') THEN
14808                 -- title and author should not be split
14809                 -- FIXME: once oils_xpath can grok XPATH 2.0 functions, we can use
14810                 -- string-join in the xpath and remove this special case
14811                         SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14812                         IF (value IS NOT NULL AND value <> '') THEN
14813                                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14814                                     VALUES (NEW.id, adef.id, atype, adef.code, value);
14815                 END IF;
14816             ELSE
14817                 pos := 1;
14818
14819                 LOOP
14820                             SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14821
14822                             IF (value IS NOT NULL AND value <> '') THEN
14823                                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14824                                             VALUES (NEW.id, adef.id, atype, adef.code, value);
14825                     ELSE
14826                         EXIT;
14827                                 END IF;
14828
14829                     pos := pos + 1;
14830                 END LOOP;
14831             END IF;
14832
14833                 END IF;
14834
14835         END LOOP;
14836
14837         RETURN NULL;
14838 END;
14839 $function$ LANGUAGE PLPGSQL;
14840
14841 UPDATE config.metabib_field SET label = name;
14842 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14843
14844 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
14845          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
14846
14847 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
14848
14849 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
14850
14851 CREATE TABLE config.metabib_search_alias (
14852     alias       TEXT    PRIMARY KEY,
14853     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
14854     field       INT     REFERENCES config.metabib_field (id)
14855 );
14856
14857 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
14858 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
14859 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
14860 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
14861 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
14862 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
14863 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
14864 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
14865
14866 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
14867 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
14868 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
14869 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
14870 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
14871 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
14872 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
14873 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
14874 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
14875 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
14876 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
14877 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
14878 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
14879
14880 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
14881 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
14882 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
14883 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
14884 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
14885 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
14886 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
14887 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
14888
14889 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
14890 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
14891 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
14892 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
14893 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
14894 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
14895
14896 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
14897 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
14898 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
14899
14900 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
14901 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;
14902 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;
14903 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;
14904 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;
14905
14906 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
14907 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
14908 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
14909 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
14910
14911 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
14912 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
14913 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
14914 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
14915 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
14916 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
14917 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
14918 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
14919 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
14920 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
14921 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
14922 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
14923 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
14924 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
14925
14926 CREATE TABLE asset.opac_visible_copies (
14927   id        BIGINT primary key, -- copy id
14928   record    BIGINT,
14929   circ_lib  INTEGER
14930 );
14931 COMMENT ON TABLE asset.opac_visible_copies IS $$
14932 Materialized view of copies that are visible in the OPAC, used by
14933 search.query_parser_fts() to speed up OPAC visibility checks on large
14934 databases.  Contents are maintained by a set of triggers.
14935 $$;
14936 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
14937
14938 CREATE OR REPLACE FUNCTION search.query_parser_fts (
14939
14940     param_search_ou INT,
14941     param_depth     INT,
14942     param_query     TEXT,
14943     param_statuses  INT[],
14944     param_locations INT[],
14945     param_offset    INT,
14946     param_check     INT,
14947     param_limit     INT,
14948     metarecord      BOOL,
14949     staff           BOOL
14950  
14951 ) RETURNS SETOF search.search_result AS $func$
14952 DECLARE
14953
14954     current_res         search.search_result%ROWTYPE;
14955     search_org_list     INT[];
14956
14957     check_limit         INT;
14958     core_limit          INT;
14959     core_offset         INT;
14960     tmp_int             INT;
14961
14962     core_result         RECORD;
14963     core_cursor         REFCURSOR;
14964     core_rel_query      TEXT;
14965
14966     total_count         INT := 0;
14967     check_count         INT := 0;
14968     deleted_count       INT := 0;
14969     visible_count       INT := 0;
14970     excluded_count      INT := 0;
14971
14972 BEGIN
14973
14974     check_limit := COALESCE( param_check, 1000 );
14975     core_limit  := COALESCE( param_limit, 25000 );
14976     core_offset := COALESCE( param_offset, 0 );
14977
14978     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
14979
14980     IF param_search_ou > 0 THEN
14981         IF param_depth IS NOT NULL THEN
14982             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
14983         ELSE
14984             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
14985         END IF;
14986     ELSIF param_search_ou < 0 THEN
14987         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
14988     ELSIF param_search_ou = 0 THEN
14989         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
14990     END IF;
14991
14992     OPEN core_cursor FOR EXECUTE param_query;
14993
14994     LOOP
14995
14996         FETCH core_cursor INTO core_result;
14997         EXIT WHEN NOT FOUND;
14998         EXIT WHEN total_count >= core_limit;
14999
15000         total_count := total_count + 1;
15001
15002         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
15003
15004         check_count := check_count + 1;
15005
15006         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15007         IF NOT FOUND THEN
15008             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
15009             deleted_count := deleted_count + 1;
15010             CONTINUE;
15011         END IF;
15012
15013         PERFORM 1
15014           FROM  biblio.record_entry b
15015                 JOIN config.bib_source s ON (b.source = s.id)
15016           WHERE s.transcendant
15017                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15018
15019         IF FOUND THEN
15020             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
15021             visible_count := visible_count + 1;
15022
15023             current_res.id = core_result.id;
15024             current_res.rel = core_result.rel;
15025
15026             tmp_int := 1;
15027             IF metarecord THEN
15028                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15029             END IF;
15030
15031             IF tmp_int = 1 THEN
15032                 current_res.record = core_result.records[1];
15033             ELSE
15034                 current_res.record = NULL;
15035             END IF;
15036
15037             RETURN NEXT current_res;
15038
15039             CONTINUE;
15040         END IF;
15041
15042         PERFORM 1
15043           FROM  asset.call_number cn
15044                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
15045                 JOIN asset.uri uri ON (map.uri = uri.id)
15046           WHERE NOT cn.deleted
15047                 AND cn.label = '##URI##'
15048                 AND uri.active
15049                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
15050                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15051                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15052           LIMIT 1;
15053
15054         IF FOUND THEN
15055             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
15056             visible_count := visible_count + 1;
15057
15058             current_res.id = core_result.id;
15059             current_res.rel = core_result.rel;
15060
15061             tmp_int := 1;
15062             IF metarecord THEN
15063                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15064             END IF;
15065
15066             IF tmp_int = 1 THEN
15067                 current_res.record = core_result.records[1];
15068             ELSE
15069                 current_res.record = NULL;
15070             END IF;
15071
15072             RETURN NEXT current_res;
15073
15074             CONTINUE;
15075         END IF;
15076
15077         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
15078
15079             PERFORM 1
15080               FROM  asset.call_number cn
15081                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15082               WHERE NOT cn.deleted
15083                     AND NOT cp.deleted
15084                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
15085                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15086                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15087               LIMIT 1;
15088
15089             IF NOT FOUND THEN
15090                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
15091                 excluded_count := excluded_count + 1;
15092                 CONTINUE;
15093             END IF;
15094
15095         END IF;
15096
15097         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
15098
15099             PERFORM 1
15100               FROM  asset.call_number cn
15101                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15102               WHERE NOT cn.deleted
15103                     AND NOT cp.deleted
15104                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
15105                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15106                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15107               LIMIT 1;
15108
15109             IF NOT FOUND THEN
15110                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
15111                 excluded_count := excluded_count + 1;
15112                 CONTINUE;
15113             END IF;
15114
15115         END IF;
15116
15117         IF staff IS NULL OR NOT staff THEN
15118
15119             PERFORM 1
15120               FROM  asset.opac_visible_copies
15121               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15122                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15123               LIMIT 1;
15124
15125             IF NOT FOUND THEN
15126                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15127                 excluded_count := excluded_count + 1;
15128                 CONTINUE;
15129             END IF;
15130
15131         ELSE
15132
15133             PERFORM 1
15134               FROM  asset.call_number cn
15135                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15136                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15137               WHERE NOT cn.deleted
15138                     AND NOT cp.deleted
15139                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15140                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15141               LIMIT 1;
15142
15143             IF NOT FOUND THEN
15144
15145                 PERFORM 1
15146                   FROM  asset.call_number cn
15147                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15148                   LIMIT 1;
15149
15150                 IF 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             END IF;
15157
15158         END IF;
15159
15160         visible_count := visible_count + 1;
15161
15162         current_res.id = core_result.id;
15163         current_res.rel = core_result.rel;
15164
15165         tmp_int := 1;
15166         IF metarecord THEN
15167             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15168         END IF;
15169
15170         IF tmp_int = 1 THEN
15171             current_res.record = core_result.records[1];
15172         ELSE
15173             current_res.record = NULL;
15174         END IF;
15175
15176         RETURN NEXT current_res;
15177
15178         IF visible_count % 1000 = 0 THEN
15179             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15180         END IF;
15181
15182     END LOOP;
15183
15184     current_res.id = NULL;
15185     current_res.rel = NULL;
15186     current_res.record = NULL;
15187     current_res.total = total_count;
15188     current_res.checked = check_count;
15189     current_res.deleted = deleted_count;
15190     current_res.visible = visible_count;
15191     current_res.excluded = excluded_count;
15192
15193     CLOSE core_cursor;
15194
15195     RETURN NEXT current_res;
15196
15197 END;
15198 $func$ LANGUAGE PLPGSQL;
15199
15200 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15201 ALTER TABLE biblio.record_entry
15202          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15203          REFERENCES actor.org_unit (id)
15204          DEFERRABLE INITIALLY DEFERRED;
15205
15206 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15207
15208 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15209 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15210
15211 DROP VIEW auditor.biblio_record_entry_lifecycle;
15212
15213 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15214
15215 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15216         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15217 $$ LANGUAGE SQL STRICT IMMUTABLE;
15218
15219 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15220     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15221 $$ LANGUAGE SQL STRICT IMMUTABLE;
15222
15223 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15224     return lc(shift);
15225 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15226
15227 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15228     return uc(shift);
15229 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15230
15231 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15232     use Unicode::Normalize;
15233
15234     my $x = NFD(shift);
15235     $x =~ s/\pM+//go;
15236     return $x;
15237
15238 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15239
15240 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15241     use Unicode::Normalize;
15242
15243     my $x = NFC(shift);
15244     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15245     return $x;
15246
15247 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15248
15249 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15250 DECLARE
15251     setting RECORD;
15252     cur_org INT;
15253 BEGIN
15254     cur_org := org_id;
15255     LOOP
15256         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15257         IF FOUND THEN
15258             RETURN NEXT setting;
15259         END IF;
15260         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15261         EXIT WHEN cur_org IS NULL;
15262     END LOOP;
15263     RETURN;
15264 END;
15265 $$ LANGUAGE plpgsql STABLE;
15266
15267 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15268 DECLARE
15269     counter INT;
15270     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15271 BEGIN
15272
15273     SELECT  COUNT(*) INTO counter
15274       FROM  oils_xpath_table(
15275                 'id',
15276                 'marc',
15277                 'acq.lineitem',
15278                 '//*[@tag="' || tag || '"]',
15279                 'id=' || lineitem
15280             ) as t(i int,c text);
15281
15282     FOR i IN 1 .. counter LOOP
15283         FOR lida IN
15284             SELECT  *
15285               FROM  (   SELECT  id,i,t,v
15286                           FROM  oils_xpath_table(
15287                                     'id',
15288                                     'marc',
15289                                     'acq.lineitem',
15290                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15291                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15292                                     'id=' || lineitem
15293                                 ) as t(id int,t text,v text)
15294                     )x
15295         LOOP
15296             RETURN NEXT lida;
15297         END LOOP;
15298     END LOOP;
15299
15300     RETURN;
15301 END;
15302 $$ LANGUAGE PLPGSQL;
15303
15304 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15305 DECLARE
15306     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15307     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15308     result      config.i18n_core%ROWTYPE;
15309     fallback    TEXT;
15310     keyfield    TEXT := keyclass || '.' || keycol;
15311 BEGIN
15312
15313     -- Try the full locale
15314     SELECT  * INTO result
15315       FROM  config.i18n_core
15316       WHERE fq_field = keyfield
15317             AND identity_value = keyvalue
15318             AND translation = locale;
15319
15320     -- Try just the language
15321     IF NOT FOUND THEN
15322         SELECT  * INTO result
15323           FROM  config.i18n_core
15324           WHERE fq_field = keyfield
15325                 AND identity_value = keyvalue
15326                 AND translation = language;
15327     END IF;
15328
15329     -- Fall back to the string we passed in in the first place
15330     IF NOT FOUND THEN
15331     EXECUTE
15332             'SELECT ' ||
15333                 keycol ||
15334             ' FROM ' || keytable ||
15335             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15336                 INTO fallback;
15337         RETURN fallback;
15338     END IF;
15339
15340     RETURN result.string;
15341 END;
15342 $func$ LANGUAGE PLPGSQL STABLE;
15343
15344 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15345
15346 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15347
15348 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15349
15350 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15351     3, 1, 'delivered_but_lost',
15352     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15353
15354 CREATE TABLE config.global_flag (
15355     label   TEXT    NOT NULL
15356 ) INHERITS (config.internal_flag);
15357 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15358
15359 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
15360     VALUES (
15361         'cat.bib.use_id_for_tcn',
15362         oils_i18n_gettext(
15363             'cat.bib.use_id_for_tcn',
15364             'Cat: Use Internal ID for TCN Value',
15365             'cgf', 
15366             'label'
15367         )
15368     );
15369
15370 -- resolves performance issue noted by EG Indiana
15371
15372 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15373
15374 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15375
15376 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15377     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15378 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15379     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15380 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15381     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15382 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15383     (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 );
15384 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15385     (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 );
15386 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15387     (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 );
15388 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15389     (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 );
15390 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15391     (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 );
15392 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15393     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15394
15395 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15396  
15397
15398 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15399
15400 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15401 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15402 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15403 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15404 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15405 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15406
15407 CREATE TABLE metabib.identifier_field_entry (
15408         id              BIGSERIAL       PRIMARY KEY,
15409         source          BIGINT          NOT NULL,
15410         field           INT             NOT NULL,
15411         value           TEXT            NOT NULL,
15412         index_vector    tsvector        NOT NULL
15413 );
15414 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15415         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15416         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15417
15418 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15419 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15420     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15421 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15422
15423 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15424     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15425 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15426     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15427
15428 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15429     use Business::ISBN;
15430     use strict;
15431     use warnings;
15432
15433     # For each ISBN found in a single string containing a set of ISBNs:
15434     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15435     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15436
15437     my $input = shift;
15438     my $output = '';
15439
15440     foreach my $word (split(/\s/, $input)) {
15441         my $isbn = Business::ISBN->new($word);
15442
15443         # First check the checksum; if it is not valid, fix it and add the original
15444         # bad-checksum ISBN to the output
15445         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15446             $output .= $isbn->isbn() . " ";
15447             $isbn->fix_checksum();
15448         }
15449
15450         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15451         # and add the normalized original ISBN to the output
15452         if ($isbn && $isbn->is_valid()) {
15453             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15454             $output .= $isbn->isbn . " ";
15455
15456             # If we successfully converted the ISBN to its counterpart, add the
15457             # converted ISBN to the output as well
15458             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15459         }
15460     }
15461     return $output if $output;
15462
15463     # If there were no valid ISBNs, just return the raw input
15464     return $input;
15465 $func$ LANGUAGE PLPERLU;
15466
15467 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15468 /*
15469  * Copyright (C) 2010 Merrimack Valley Library Consortium
15470  * Jason Stephenson <jstephenson@mvlc.org>
15471  * Copyright (C) 2010 Laurentian University
15472  * Dan Scott <dscott@laurentian.ca>
15473  *
15474  * The translate_isbn1013 function takes an input ISBN and returns the
15475  * following in a single space-delimited string if the input ISBN is valid:
15476  *   - The normalized input ISBN (hyphens stripped)
15477  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15478  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15479  */
15480 $$;
15481
15482 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15483 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15484 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15485 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15486 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15487 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15488
15489 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15490         'ISBN 10/13 conversion',
15491         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15492         'translate_isbn1013',
15493         0
15494 );
15495
15496 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15497         'Replace',
15498         'Replace all occurences of first parameter in the string with the second parameter.',
15499         'replace',
15500         2
15501 );
15502
15503 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15504     SELECT  m.id, i.id, 1
15505       FROM  config.metabib_field m,
15506             config.index_normalizer i
15507       WHERE i.func IN ('first_word')
15508             AND m.id IN (18);
15509
15510 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15511     SELECT  m.id, i.id, 2
15512       FROM  config.metabib_field m,
15513             config.index_normalizer i
15514       WHERE i.func IN ('translate_isbn1013')
15515             AND m.id IN (18);
15516
15517 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15518     SELECT  m.id, i.id, $$['-','']$$
15519       FROM  config.metabib_field m,
15520             config.index_normalizer i
15521       WHERE i.func IN ('replace')
15522             AND m.id IN (19);
15523
15524 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15525     SELECT  m.id, i.id, $$[' ','']$$
15526       FROM  config.metabib_field m,
15527             config.index_normalizer i
15528       WHERE i.func IN ('replace')
15529             AND m.id IN (19);
15530
15531 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15532
15533 UPDATE  config.metabib_field_index_norm_map
15534   SET   params = REPLACE(params,E'\'','"')
15535   WHERE params IS NOT NULL AND params <> '';
15536
15537 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15538
15539 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15540
15541 ALTER TABLE config.circ_modifier
15542         ADD COLUMN avg_wait_time INTERVAL;
15543
15544 --CREATE TABLE actor.usr_password_reset (
15545 --  id SERIAL PRIMARY KEY,
15546 --  uuid TEXT NOT NULL, 
15547 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15548 --  request_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), 
15549 --  has_been_reset BOOL NOT NULL DEFAULT false
15550 --);
15551 --COMMENT ON TABLE actor.usr_password_reset IS $$
15552 --/*
15553 -- * Copyright (C) 2010 Laurentian University
15554 -- * Dan Scott <dscott@laurentian.ca>
15555 -- *
15556 -- * Self-serve password reset requests
15557 -- *
15558 -- * ****
15559 -- *
15560 -- * This program is free software; you can redistribute it and/or
15561 -- * modify it under the terms of the GNU General Public License
15562 -- * as published by the Free Software Foundation; either version 2
15563 -- * of the License, or (at your option) any later version.
15564 -- *
15565 -- * This program is distributed in the hope that it will be useful,
15566 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15567 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15568 -- * GNU General Public License for more details.
15569 -- */
15570 --$$;
15571 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15572 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15573 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15574 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15575
15576 -- Use the identifier search class tsconfig
15577 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15578 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15579     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15580     FOR EACH ROW
15581     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15582
15583 INSERT INTO config.global_flag (name,label,enabled)
15584     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15585 INSERT INTO config.global_flag (name,label,enabled)
15586     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15587
15588 -- turn a JSON scalar into an SQL TEXT value
15589 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15590     use JSON::XS;                    
15591     my $json = shift();
15592     my $txt;
15593     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15594     return undef if ($@);
15595     return $txt
15596 $f$ LANGUAGE PLPERLU;
15597
15598 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15599 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15600 DECLARE
15601     c               action.circulation%ROWTYPE;
15602     view_age        INTERVAL;
15603     usr_view_age    actor.usr_setting%ROWTYPE;
15604     usr_view_start  actor.usr_setting%ROWTYPE;
15605 BEGIN
15606     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15607     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15608
15609     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15610         -- User opted in and supplied a retention age
15611         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15612             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15613         ELSE
15614             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15615         END IF;
15616     ELSIF usr_view_start.value IS NOT NULL THEN
15617         -- User opted in
15618         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15619     ELSE
15620         -- User did not opt in
15621         RETURN;
15622     END IF;
15623
15624     FOR c IN
15625         SELECT  *
15626           FROM  action.circulation
15627           WHERE usr = usr_id
15628                 AND parent_circ IS NULL
15629                 AND xact_start > NOW() - view_age
15630           ORDER BY xact_start
15631     LOOP
15632         RETURN NEXT c;
15633     END LOOP;
15634
15635     RETURN;
15636 END;
15637 $func$ LANGUAGE PLPGSQL;
15638
15639 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15640 DECLARE
15641     usr_keep_age    actor.usr_setting%ROWTYPE;
15642     usr_keep_start  actor.usr_setting%ROWTYPE;
15643     org_keep_age    INTERVAL;
15644     org_keep_count  INT;
15645
15646     keep_age        INTERVAL;
15647
15648     target_acp      RECORD;
15649     circ_chain_head action.circulation%ROWTYPE;
15650     circ_chain_tail action.circulation%ROWTYPE;
15651
15652     purge_position  INT;
15653     count_purged    INT;
15654 BEGIN
15655
15656     count_purged := 0;
15657
15658     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15659
15660     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15661     IF org_keep_count IS NULL THEN
15662         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15663     END IF;
15664
15665     -- First, find copies with more than keep_count non-renewal circs
15666     FOR target_acp IN
15667         SELECT  target_copy,
15668                 COUNT(*) AS total_real_circs
15669           FROM  action.circulation
15670           WHERE parent_circ IS NULL
15671                 AND xact_finish IS NOT NULL
15672           GROUP BY target_copy
15673           HAVING COUNT(*) > org_keep_count
15674     LOOP
15675         purge_position := 0;
15676         -- And, for those, select circs that are finished and older than keep_age
15677         FOR circ_chain_head IN
15678             SELECT  *
15679               FROM  action.circulation
15680               WHERE target_copy = target_acp.target_copy
15681                     AND parent_circ IS NULL
15682               ORDER BY xact_start
15683         LOOP
15684
15685             -- Stop once we've purged enough circs to hit org_keep_count
15686             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15687
15688             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15689             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15690
15691             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15692             usr_keep_age.value := NULL;
15693             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15694
15695             usr_keep_start.value := NULL;
15696             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15697
15698             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15699                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15700                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15701                 ELSE
15702                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15703                 END IF;
15704             ELSIF usr_keep_start.value IS NOT NULL THEN
15705                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15706             ELSE
15707                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15708             END IF;
15709
15710             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15711
15712             -- We've passed the purging tests, purge the circ chain starting at the end
15713             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15714             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15715                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15716                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15717             END LOOP;
15718
15719             count_purged := count_purged + 1;
15720             purge_position := purge_position + 1;
15721
15722         END LOOP;
15723     END LOOP;
15724 END;
15725 $func$ LANGUAGE PLPGSQL;
15726
15727 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15728 DECLARE
15729     h               action.hold_request%ROWTYPE;
15730     view_age        INTERVAL;
15731     view_count      INT;
15732     usr_view_count  actor.usr_setting%ROWTYPE;
15733     usr_view_age    actor.usr_setting%ROWTYPE;
15734     usr_view_start  actor.usr_setting%ROWTYPE;
15735 BEGIN
15736     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15737     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15738     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15739
15740     FOR h IN
15741         SELECT  *
15742           FROM  action.hold_request
15743           WHERE usr = usr_id
15744                 AND fulfillment_time IS NULL
15745                 AND cancel_time IS NULL
15746           ORDER BY request_time DESC
15747     LOOP
15748         RETURN NEXT h;
15749     END LOOP;
15750
15751     IF usr_view_start.value IS NULL THEN
15752         RETURN;
15753     END IF;
15754
15755     IF usr_view_age.value IS NOT NULL THEN
15756         -- User opted in and supplied a retention age
15757         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15758             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15759         ELSE
15760             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15761         END IF;
15762     ELSE
15763         -- User opted in
15764         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15765     END IF;
15766
15767     IF usr_view_count.value IS NOT NULL THEN
15768         view_count := oils_json_to_text(usr_view_count.value)::INT;
15769     ELSE
15770         view_count := 1000;
15771     END IF;
15772
15773     -- show some fulfilled/canceled holds
15774     FOR h IN
15775         SELECT  *
15776           FROM  action.hold_request
15777           WHERE usr = usr_id
15778                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
15779                 AND request_time > NOW() - view_age
15780           ORDER BY request_time DESC
15781           LIMIT view_count
15782     LOOP
15783         RETURN NEXT h;
15784     END LOOP;
15785
15786     RETURN;
15787 END;
15788 $func$ LANGUAGE PLPGSQL;
15789
15790 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
15791
15792 DROP TABLE IF EXISTS serial.index_summary CASCADE;
15793
15794 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
15795
15796 DROP TABLE IF EXISTS serial.issuance CASCADE;
15797
15798 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
15799
15800 DROP TABLE IF EXISTS serial.subscription CASCADE;
15801
15802 CREATE TABLE asset.copy_template (
15803         id             SERIAL   PRIMARY KEY,
15804         owning_lib     INT      NOT NULL
15805                                 REFERENCES actor.org_unit (id)
15806                                 DEFERRABLE INITIALLY DEFERRED,
15807         creator        BIGINT   NOT NULL
15808                                 REFERENCES actor.usr (id)
15809                                 DEFERRABLE INITIALLY DEFERRED,
15810         editor         BIGINT   NOT NULL
15811                                 REFERENCES actor.usr (id)
15812                                 DEFERRABLE INITIALLY DEFERRED,
15813         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15814         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15815         name           TEXT     NOT NULL,
15816         -- columns above this point are attributes of the template itself
15817         -- columns after this point are attributes of the copy this template modifies/creates
15818         circ_lib       INT      REFERENCES actor.org_unit (id)
15819                                 DEFERRABLE INITIALLY DEFERRED,
15820         status         INT      REFERENCES config.copy_status (id)
15821                                 DEFERRABLE INITIALLY DEFERRED,
15822         location       INT      REFERENCES asset.copy_location (id)
15823                                 DEFERRABLE INITIALLY DEFERRED,
15824         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
15825                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
15826         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
15827                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
15828         age_protect    INT,
15829         circulate      BOOL,
15830         deposit        BOOL,
15831         ref            BOOL,
15832         holdable       BOOL,
15833         deposit_amount NUMERIC(6,2),
15834         price          NUMERIC(8,2),
15835         circ_modifier  TEXT,
15836         circ_as_type   TEXT,
15837         alert_message  TEXT,
15838         opac_visible   BOOL,
15839         floating       BOOL,
15840         mint_condition BOOL
15841 );
15842
15843 CREATE TABLE serial.subscription (
15844         id                     SERIAL       PRIMARY KEY,
15845         owning_lib             INT          NOT NULL DEFAULT 1
15846                                             REFERENCES actor.org_unit (id)
15847                                             ON DELETE SET NULL
15848                                             DEFERRABLE INITIALLY DEFERRED,
15849         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
15850         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
15851         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
15852                                             ON DELETE SET NULL
15853                                             DEFERRABLE INITIALLY DEFERRED,
15854         expected_date_offset   INTERVAL
15855         -- acquisitions/business-side tables link to here
15856 );
15857 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
15858 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
15859
15860 --at least one distribution per org_unit holding issues
15861 CREATE TABLE serial.distribution (
15862         id                    SERIAL  PRIMARY KEY,
15863         record_entry          BIGINT  REFERENCES serial.record_entry (id)
15864                                       ON DELETE SET NULL
15865                                       DEFERRABLE INITIALLY DEFERRED,
15866         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
15867                                           summary_method IS NULL
15868                                           OR summary_method IN ( 'add_to_sre',
15869                                           'merge_with_sre', 'use_sre_only',
15870                                           'use_sdist_only')),
15871         subscription          INT     NOT NULL
15872                                       REFERENCES serial.subscription (id)
15873                                                                   ON DELETE CASCADE
15874                                                                   DEFERRABLE INITIALLY DEFERRED,
15875         holding_lib           INT     NOT NULL
15876                                       REFERENCES actor.org_unit (id)
15877                                                                   DEFERRABLE INITIALLY DEFERRED,
15878         label                 TEXT    NOT NULL,
15879         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
15880                                       DEFERRABLE INITIALLY DEFERRED,
15881         receive_unit_template INT     REFERENCES asset.copy_template (id)
15882                                       DEFERRABLE INITIALLY DEFERRED,
15883         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
15884                                       DEFERRABLE INITIALLY DEFERRED,
15885         bind_unit_template    INT     REFERENCES asset.copy_template (id)
15886                                       DEFERRABLE INITIALLY DEFERRED,
15887         unit_label_prefix     TEXT,
15888         unit_label_suffix     TEXT
15889 );
15890 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
15891 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
15892
15893 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
15894
15895 CREATE TABLE serial.stream (
15896         id              SERIAL  PRIMARY KEY,
15897         distribution    INT     NOT NULL
15898                                 REFERENCES serial.distribution (id)
15899                                 ON DELETE CASCADE
15900                                 DEFERRABLE INITIALLY DEFERRED,
15901         routing_label   TEXT
15902 );
15903 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
15904
15905 CREATE UNIQUE INDEX label_once_per_dist
15906         ON serial.stream (distribution, routing_label)
15907         WHERE routing_label IS NOT NULL;
15908
15909 CREATE TABLE serial.routing_list_user (
15910         id             SERIAL       PRIMARY KEY,
15911         stream         INT          NOT NULL
15912                                     REFERENCES serial.stream
15913                                     ON DELETE CASCADE
15914                                     DEFERRABLE INITIALLY DEFERRED,
15915         pos            INT          NOT NULL DEFAULT 1,
15916         reader         INT          REFERENCES actor.usr
15917                                     ON DELETE CASCADE
15918                                     DEFERRABLE INITIALLY DEFERRED,
15919         department     TEXT,
15920         note           TEXT,
15921         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
15922         CONSTRAINT reader_or_dept CHECK
15923         (
15924             -- Recipient is a person or a department, but not both
15925                 (reader IS NOT NULL AND department IS NULL) OR
15926                 (reader IS NULL AND department IS NOT NULL)
15927         )
15928 );
15929 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
15930 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
15931
15932 CREATE TABLE serial.caption_and_pattern (
15933         id           SERIAL       PRIMARY KEY,
15934         subscription INT          NOT NULL REFERENCES serial.subscription (id)
15935                                   ON DELETE CASCADE
15936                                   DEFERRABLE INITIALLY DEFERRED,
15937         type         TEXT         NOT NULL
15938                                   CONSTRAINT cap_type CHECK ( type in
15939                                   ( 'basic', 'supplement', 'index' )),
15940         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
15941         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
15942         end_date     TIMESTAMP WITH TIME ZONE,
15943         active       BOOL         NOT NULL DEFAULT FALSE,
15944         pattern_code TEXT         NOT NULL,       -- must contain JSON
15945         enum_1       TEXT,
15946         enum_2       TEXT,
15947         enum_3       TEXT,
15948         enum_4       TEXT,
15949         enum_5       TEXT,
15950         enum_6       TEXT,
15951         chron_1      TEXT,
15952         chron_2      TEXT,
15953         chron_3      TEXT,
15954         chron_4      TEXT,
15955         chron_5      TEXT
15956 );
15957 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
15958
15959 CREATE TABLE serial.issuance (
15960         id              SERIAL    PRIMARY KEY,
15961         creator         INT       NOT NULL
15962                                   REFERENCES actor.usr (id)
15963                                                           DEFERRABLE INITIALLY DEFERRED,
15964         editor          INT       NOT NULL
15965                                   REFERENCES actor.usr (id)
15966                                   DEFERRABLE INITIALLY DEFERRED,
15967         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15968         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15969         subscription    INT       NOT NULL
15970                                   REFERENCES serial.subscription (id)
15971                                   ON DELETE CASCADE
15972                                   DEFERRABLE INITIALLY DEFERRED,
15973         label           TEXT,
15974         date_published  TIMESTAMP WITH TIME ZONE,
15975         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
15976                               DEFERRABLE INITIALLY DEFERRED,
15977         holding_code    TEXT,
15978         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
15979                                   (
15980                                       holding_type IS NULL
15981                                       OR holding_type IN ('basic','supplement','index')
15982                                   ),
15983         holding_link_id INT
15984         -- TODO: add columns for separate enumeration/chronology values
15985 );
15986 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
15987 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
15988 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
15989
15990 CREATE TABLE serial.unit (
15991         label           TEXT,
15992         label_sort_key  TEXT,
15993         contents        TEXT    NOT NULL
15994 ) INHERITS (asset.copy);
15995 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
15996 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
15997 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
15998 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
15999 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
16000
16001 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
16002
16003 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
16004
16005 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16006
16007 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16008
16009 CREATE TABLE serial.item (
16010         id              SERIAL  PRIMARY KEY,
16011         creator         INT     NOT NULL
16012                                 REFERENCES actor.usr (id)
16013                                 DEFERRABLE INITIALLY DEFERRED,
16014         editor          INT     NOT NULL
16015                                 REFERENCES actor.usr (id)
16016                                 DEFERRABLE INITIALLY DEFERRED,
16017         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16018         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16019         issuance        INT     NOT NULL
16020                                 REFERENCES serial.issuance (id)
16021                                 ON DELETE CASCADE
16022                                 DEFERRABLE INITIALLY DEFERRED,
16023         stream          INT     NOT NULL
16024                                 REFERENCES serial.stream (id)
16025                                 ON DELETE CASCADE
16026                                 DEFERRABLE INITIALLY DEFERRED,
16027         unit            INT     REFERENCES serial.unit (id)
16028                                 ON DELETE SET NULL
16029                                 DEFERRABLE INITIALLY DEFERRED,
16030         uri             INT     REFERENCES asset.uri (id)
16031                                 ON DELETE SET NULL
16032                                 DEFERRABLE INITIALLY DEFERRED,
16033         date_expected   TIMESTAMP WITH TIME ZONE,
16034         date_received   TIMESTAMP WITH TIME ZONE,
16035         status          TEXT    CONSTRAINT valid_status CHECK (
16036                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
16037                                'Expected', 'Not Held', 'Not Published', 'Received'))
16038                             DEFAULT 'Expected',
16039         shadowed        BOOL    NOT NULL DEFAULT FALSE
16040 );
16041 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
16042 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
16043 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
16044 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
16045 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
16046 CREATE INDEX serial_item_status_idx ON serial.item (status);
16047
16048 CREATE TABLE serial.item_note (
16049         id          SERIAL  PRIMARY KEY,
16050         item        INT     NOT NULL
16051                             REFERENCES serial.item (id)
16052                             ON DELETE CASCADE
16053                             DEFERRABLE INITIALLY DEFERRED,
16054         creator     INT     NOT NULL
16055                             REFERENCES actor.usr (id)
16056                             DEFERRABLE INITIALLY DEFERRED,
16057         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
16058         pub         BOOL    NOT NULL    DEFAULT FALSE,
16059         title       TEXT    NOT NULL,
16060         value       TEXT    NOT NULL
16061 );
16062 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
16063
16064 CREATE TABLE serial.basic_summary (
16065         id                  SERIAL  PRIMARY KEY,
16066         distribution        INT     NOT NULL
16067                                     REFERENCES serial.distribution (id)
16068                                     ON DELETE CASCADE
16069                                     DEFERRABLE INITIALLY DEFERRED,
16070         generated_coverage  TEXT    NOT NULL,
16071         textual_holdings    TEXT,
16072         show_generated      BOOL    NOT NULL DEFAULT TRUE
16073 );
16074 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
16075
16076 CREATE TABLE serial.supplement_summary (
16077         id                  SERIAL  PRIMARY KEY,
16078         distribution        INT     NOT NULL
16079                                     REFERENCES serial.distribution (id)
16080                                     ON DELETE CASCADE
16081                                     DEFERRABLE INITIALLY DEFERRED,
16082         generated_coverage  TEXT    NOT NULL,
16083         textual_holdings    TEXT,
16084         show_generated      BOOL    NOT NULL DEFAULT TRUE
16085 );
16086 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
16087
16088 CREATE TABLE serial.index_summary (
16089         id                  SERIAL  PRIMARY KEY,
16090         distribution        INT     NOT NULL
16091                                     REFERENCES serial.distribution (id)
16092                                     ON DELETE CASCADE
16093                                     DEFERRABLE INITIALLY DEFERRED,
16094         generated_coverage  TEXT    NOT NULL,
16095         textual_holdings    TEXT,
16096         show_generated      BOOL    NOT NULL DEFAULT TRUE
16097 );
16098 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
16099
16100 -- 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.
16101
16102 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
16103 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16104
16105 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
16106 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;
16107
16108 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
16109 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16110
16111 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
16112 RETURNS INTEGER AS $$
16113 BEGIN
16114         RETURN EXTRACT( EPOCH FROM interval_val );
16115 END;
16116 $$ LANGUAGE plpgsql;
16117
16118 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
16119 RETURNS INTEGER AS $$
16120 BEGIN
16121         RETURN config.interval_to_seconds( interval_string::INTERVAL );
16122 END;
16123 $$ LANGUAGE plpgsql;
16124
16125 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
16126     'temp',
16127     oils_i18n_gettext(
16128         'temp',
16129         'Temporary bucket which gets deleted after use.',
16130         'cbrebt',
16131         'label'
16132     )
16133 );
16134
16135 -- 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.
16136
16137 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
16138 BEGIN
16139
16140     IF xml_is_well_formed(NEW.marc) THEN
16141         RETURN NEW;
16142     ELSE
16143         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16144     END IF;
16145     
16146 END;
16147 $func$ LANGUAGE PLPGSQL;
16148
16149 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();
16150
16151 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();
16152
16153 ALTER TABLE serial.record_entry
16154         ALTER COLUMN marc DROP NOT NULL;
16155
16156 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16157 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16158 <xsl:stylesheet
16159     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16160     xmlns:marc="http://www.loc.gov/MARC21/slim"
16161     version="1.0">
16162 <!--
16163 Copyright (C) 2010  Equinox Software, Inc.
16164 Galen Charlton <gmc@esilibrary.cOM.
16165
16166 This program is free software; you can redistribute it and/or
16167 modify it under the terms of the GNU General Public License
16168 as published by the Free Software Foundation; either version 2
16169 of the License, or (at your option) any later version.
16170
16171 This program is distributed in the hope that it will be useful,
16172 but WITHOUT ANY WARRANTY; without even the implied warranty of
16173 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16174 GNU General Public License for more details.
16175
16176 marc21_expand_880.xsl - stylesheet used during indexing to
16177                         map alternative graphical representations
16178                         of MARC fields stored in 880 fields
16179                         to the corresponding tag name and value.
16180
16181 For example, if a MARC record for a Chinese book has
16182
16183 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16184 880.00 $6 245-01/$1 $a八十三年短篇小說選
16185
16186 this stylesheet will transform it to the equivalent of
16187
16188 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16189 245.00 $6 245-01/$1 $a八十三年短篇小說選
16190
16191 -->
16192     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16193
16194     <xsl:template match="@*|node()">
16195         <xsl:copy>
16196             <xsl:apply-templates select="@*|node()"/>
16197         </xsl:copy>
16198     </xsl:template>
16199
16200     <xsl:template match="//marc:datafield[@tag='880']">
16201         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16202             <marc:datafield>
16203                 <xsl:attribute name="tag">
16204                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16205                 </xsl:attribute>
16206                 <xsl:attribute name="ind1">
16207                     <xsl:value-of select="@ind1" />
16208                 </xsl:attribute>
16209                 <xsl:attribute name="ind2">
16210                     <xsl:value-of select="@ind2" />
16211                 </xsl:attribute>
16212                 <xsl:apply-templates />
16213             </marc:datafield>
16214         </xsl:if>
16215     </xsl:template>
16216     
16217 </xsl:stylesheet>$$);
16218
16219 -- Splitting the ingest trigger up into little bits
16220
16221 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16222     flag INTEGER PRIMARY KEY
16223 ) ON COMMIT DROP;
16224 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16225
16226 -- cause failure if either of the tables we want to drop have rows
16227 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16228 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16229
16230 DROP TABLE IF EXISTS asset.copy_transparency_map;
16231 DROP TABLE IF EXISTS asset.copy_transparency;
16232
16233 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16234
16235 -- We won't necessarily use all of these, but they are here for completeness.
16236 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16237 -- Values are the EDI code value + 1000
16238
16239 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16240 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16241 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16242 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16243 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16244 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16245 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16246 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16247 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16248 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16249 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16250 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16251 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16252 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16253 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16254 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16255 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16256 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16257 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16258 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16259 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16260 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16261 ('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).'),
16262 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16263 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16264 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16265 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16266 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16267 ('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.'),
16268 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16269 ('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.'),
16270 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16271 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16272 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16273 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16274 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16275 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16276 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16277 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16278 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16279 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16280 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16281 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16282 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16283 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16284 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16285 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16286 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16287 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16288 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16289 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16290 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16291 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16292 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16293 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16294 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16295 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16296 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16297 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16298 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16299 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16300 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16301 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16302 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16303 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16304 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16305 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16306 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16307 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16308 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16309 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16310 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16311 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16312 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16313 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16314 ('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).'),
16315 ('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).'),
16316 ('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).'),
16317 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16318 ('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).'),
16319 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16320 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16321 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16322 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16323 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16324 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16325 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16326 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16327 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16328 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16329 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16330 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16331 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16332 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16333 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16334 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16335 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16336 ('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.'),
16337 ('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.'),
16338 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16339 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16340 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16341 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16342 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16343 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16344 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16345 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16346 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16347 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16348 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16349 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16350 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16351 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16352 ('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.'),
16353 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16354 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16355
16356 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16357     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16358  
16359 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16360  
16361 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16362         'Remove Parenthesized Substring',
16363         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16364         'remove_paren_substring',
16365         0
16366 );
16367
16368 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16369         'Trim Surrounding Space',
16370         'Trim leading and trailing spaces from extracted text.',
16371         'btrim',
16372         0
16373 );
16374
16375 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16376     SELECT  m.id,
16377             i.id,
16378             -2
16379       FROM  config.metabib_field m,
16380             config.index_normalizer i
16381       WHERE i.func IN ('remove_paren_substring')
16382             AND m.id IN (26);
16383
16384 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16385     SELECT  m.id,
16386             i.id,
16387             -1
16388       FROM  config.metabib_field m,
16389             config.index_normalizer i
16390       WHERE i.func IN ('btrim')
16391             AND m.id IN (26);
16392
16393 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16394 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16395 DECLARE
16396     dyn_profile     vandelay.compile_profile%ROWTYPE;
16397     replace_rule    TEXT;
16398     tmp_marc        TEXT;
16399     trgt_marc        TEXT;
16400     tmpl_marc        TEXT;
16401     match_count     INT;
16402 BEGIN
16403
16404     IF target_marc IS NULL OR template_marc IS NULL THEN
16405         -- RAISE NOTICE 'no marc for target or template record';
16406         RETURN NULL;
16407     END IF;
16408
16409     dyn_profile := vandelay.compile_profile( template_marc );
16410
16411     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16412         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16413         RETURN NULL;
16414     END IF;
16415
16416     IF dyn_profile.replace_rule <> '' THEN
16417         trgt_marc = target_marc;
16418         tmpl_marc = template_marc;
16419         replace_rule = dyn_profile.replace_rule;
16420     ELSE
16421         tmp_marc = target_marc;
16422         trgt_marc = template_marc;
16423         tmpl_marc = tmp_marc;
16424         replace_rule = dyn_profile.preserve_rule;
16425     END IF;
16426
16427     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16428
16429 END;
16430 $$ LANGUAGE PLPGSQL;
16431
16432 -- Function to generate an ephemeral overlay template from an authority record
16433 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16434
16435     use MARC::Record;
16436     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16437
16438     my $xml = shift;
16439     my $r = MARC::Record->new_from_xml( $xml );
16440
16441     return undef unless ($r);
16442
16443     my $id = shift() || $r->subfield( '901' => 'c' );
16444     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16445     return undef unless ($id); # We need an ID!
16446
16447     my $tmpl = MARC::Record->new();
16448
16449     my @rule_fields;
16450     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16451
16452         my $tag = $field->tag;
16453         my $i1 = $field->indicator(1);
16454         my $i2 = $field->indicator(2);
16455         my $sf = join '', map { $_->[0] } $field->subfields;
16456         my @data = map { @$_ } $field->subfields;
16457
16458         my @replace_them;
16459
16460         # Map the authority field to bib fields it can control.
16461         if ($tag >= 100 and $tag <= 111) {       # names
16462             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16463         } elsif ($tag eq '130') {                # uniform title
16464             @replace_them = qw/130 240 440 730 830/;
16465         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16466             @replace_them = ($tag + 500);
16467         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16468             @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/;
16469         } else {
16470             next;
16471         }
16472
16473         # Dummy up the bib-side data
16474         $tmpl->append_fields(
16475             map {
16476                 MARC::Field->new( $_, $i1, $i2, @data )
16477             } @replace_them
16478         );
16479
16480         # Construct some 'replace' rules
16481         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16482     }
16483
16484     # Insert the replace rules into the template
16485     $tmpl->append_fields(
16486         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16487     );
16488
16489     $xml = $tmpl->as_xml_record;
16490     $xml =~ s/^<\?.+?\?>$//mo;
16491     $xml =~ s/\n//sgo;
16492     $xml =~ s/>\s+</></sgo;
16493
16494     return $xml;
16495
16496 $func$ LANGUAGE PLPERLU;
16497
16498 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16499     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16500 $func$ LANGUAGE SQL;
16501
16502 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16503     SELECT authority.generate_overlay_template( $1, NULL );
16504 $func$ LANGUAGE SQL;
16505
16506 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16507 DELETE FROM config.metabib_field WHERE id = 26;
16508
16509 -- Making this a global_flag (UI accessible) instead of an internal_flag
16510 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16511     VALUES (
16512         'ingest.disable_authority_linking',
16513         oils_i18n_gettext(
16514             'ingest.disable_authority_linking',
16515             'Authority Automation: Disable bib-authority link tracking',
16516             'cgf', 
16517             'label'
16518         )
16519     );
16520 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16521 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16522
16523 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16524     VALUES (
16525         'ingest.disable_authority_auto_update',
16526         oils_i18n_gettext(
16527             'ingest.disable_authority_auto_update',
16528             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16529             'cgf', 
16530             'label'
16531         )
16532     );
16533
16534 -- Enable automated ingest of authority records; just insert the row into
16535 -- authority.record_entry and authority.full_rec will automatically be populated
16536
16537 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16538     UPDATE  biblio.record_entry
16539       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16540       WHERE id = $2;
16541     SELECT $1;
16542 $func$ LANGUAGE SQL;
16543
16544 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16545     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16546 $func$ LANGUAGE SQL;
16547
16548 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16549
16550 use MARC::Record;
16551 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16552
16553 my $xml = shift;
16554 my $r = MARC::Record->new_from_xml( $xml );
16555
16556 return_next( { tag => 'LDR', value => $r->leader } );
16557
16558 for my $f ( $r->fields ) {
16559     if ($f->is_control_field) {
16560         return_next({ tag => $f->tag, value => $f->data });
16561     } else {
16562         for my $s ($f->subfields) {
16563             return_next({
16564                 tag      => $f->tag,
16565                 ind1     => $f->indicator(1),
16566                 ind2     => $f->indicator(2),
16567                 subfield => $s->[0],
16568                 value    => $s->[1]
16569             });
16570
16571         }
16572     }
16573 }
16574
16575 return undef;
16576
16577 $func$ LANGUAGE PLPERLU;
16578
16579 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16580 DECLARE
16581     auth    authority.record_entry%ROWTYPE;
16582     output    authority.full_rec%ROWTYPE;
16583     field    RECORD;
16584 BEGIN
16585     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16586
16587     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16588         output.record := rid;
16589         output.ind1 := field.ind1;
16590         output.ind2 := field.ind2;
16591         output.tag := field.tag;
16592         output.subfield := field.subfield;
16593         IF field.subfield IS NOT NULL THEN
16594             output.value := naco_normalize(field.value, field.subfield);
16595         ELSE
16596             output.value := field.value;
16597         END IF;
16598
16599         CONTINUE WHEN output.value IS NULL;
16600
16601         RETURN NEXT output;
16602     END LOOP;
16603 END;
16604 $func$ LANGUAGE PLPGSQL;
16605
16606 -- authority.rec_descriptor appears to be unused currently
16607 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16608 BEGIN
16609     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16610 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16611 --        SELECT  auth_id, ;
16612
16613     RETURN;
16614 END;
16615 $func$ LANGUAGE PLPGSQL;
16616
16617 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16618 BEGIN
16619     DELETE FROM authority.full_rec WHERE record = auth_id;
16620     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16621         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16622
16623     RETURN;
16624 END;
16625 $func$ LANGUAGE PLPGSQL;
16626
16627 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16628 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16629 BEGIN
16630
16631     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16632         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16633         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
16634           -- Should remove matching $0 from controlled fields at the same time?
16635         RETURN NEW; -- and we're done
16636     END IF;
16637
16638     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16639         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16640
16641         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16642             RETURN NEW;
16643         END IF;
16644     END IF;
16645
16646     -- Flatten and insert the afr data
16647     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16648     IF NOT FOUND THEN
16649         PERFORM authority.reingest_authority_full_rec(NEW.id);
16650 -- authority.rec_descriptor is not currently used
16651 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16652 --        IF NOT FOUND THEN
16653 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16654 --        END IF;
16655     END IF;
16656
16657     RETURN NEW;
16658 END;
16659 $func$ LANGUAGE PLPGSQL;
16660
16661 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 ();
16662
16663 -- Some records manage to get XML namespace declarations into each element,
16664 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16665 -- This broke the old maintain_901(), so we'll make the regex more robust
16666
16667 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16668 BEGIN
16669     -- Remove any existing 901 fields before we insert the authoritative one
16670     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16671     IF TG_TABLE_SCHEMA = 'biblio' THEN
16672         NEW.marc := REGEXP_REPLACE(
16673             NEW.marc,
16674             E'(</(?:[^:]*?:)?record>)',
16675             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16676                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16677                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16678                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16679                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16680                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16681                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16682              E'</datafield>\\1'
16683         );
16684     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16685         NEW.marc := REGEXP_REPLACE(
16686             NEW.marc,
16687             E'(</(?:[^:]*?:)?record>)',
16688             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16689                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16690                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16691              E'</datafield>\\1'
16692         );
16693     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16694         NEW.marc := REGEXP_REPLACE(
16695             NEW.marc,
16696             E'(</(?:[^:]*?:)?record>)',
16697             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16698                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16699                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16700                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16701                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16702              E'</datafield>\\1'
16703         );
16704     ELSE
16705         NEW.marc := REGEXP_REPLACE(
16706             NEW.marc,
16707             E'(</(?:[^:]*?:)?record>)',
16708             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16709                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16710                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16711              E'</datafield>\\1'
16712         );
16713     END IF;
16714
16715     RETURN NEW;
16716 END;
16717 $func$ LANGUAGE PLPGSQL;
16718
16719 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16720 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16721 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16722  
16723 -- In booking, elbow room defines:
16724 --  a) how far in the future you must make a reservation on a given item if
16725 --      that item will have to transit somewhere to fulfill the reservation.
16726 --  b) how soon a reservation must be starting for the reserved item to
16727 --      be op-captured by the checkin interface.
16728
16729 -- We don't want to clobber any default_elbow room at any level:
16730
16731 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16732 DECLARE
16733     existing    actor.org_unit_setting%ROWTYPE;
16734 BEGIN
16735     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16736     IF NOT FOUND THEN
16737         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16738             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16739             'circ.booking_reservation.default_elbow_room',
16740             '"1 day"'
16741         );
16742         RETURN 1;
16743     END IF;
16744     RETURN 0;
16745 END;
16746 $$ LANGUAGE plpgsql;
16747
16748 SELECT pg_temp.default_elbow();
16749
16750 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16751
16752 -- returns the distinct set of target copy IDs from a user's visible circulation history
16753 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16754     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16755 $$ LANGUAGE SQL;
16756
16757 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
16758 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
16759 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
16760 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
16761
16762 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
16763
16764 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
16765 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
16766
16767 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16768     VALUES (
16769         'cat.maintain_control_numbers',
16770         oils_i18n_gettext(
16771             'cat.maintain_control_numbers',
16772             'Cat: Maintain 001/003/035 according to the MARC21 specification',
16773             'cgf', 
16774             'label'
16775         )
16776     );
16777
16778 INSERT INTO config.global_flag (name, label, enabled)
16779     VALUES (
16780         'circ.holds.empty_issuance_ok',
16781         oils_i18n_gettext(
16782             'circ.holds.empty_issuance_ok',
16783             'Holds: Allow holds on empty issuances',
16784             'cgf',
16785             'label'
16786         ),
16787         TRUE
16788     );
16789
16790 INSERT INTO config.global_flag (name, label, enabled)
16791     VALUES (
16792         'circ.holds.usr_not_requestor',
16793         oils_i18n_gettext(
16794             'circ.holds.usr_not_requestor',
16795             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
16796             'cgf',
16797             'label'
16798         ),
16799         TRUE
16800     );
16801
16802 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
16803 use strict;
16804 use MARC::Record;
16805 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16806 use Encode;
16807 use Unicode::Normalize;
16808
16809 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
16810 my $schema = $_TD->{table_schema};
16811 my $rec_id = $_TD->{new}{id};
16812
16813 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
16814 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
16815 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
16816     return;
16817 }
16818
16819 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
16820 my $ou_cni = 'EVRGRN';
16821
16822 my $owner;
16823 if ($schema eq 'serial') {
16824     $owner = $_TD->{new}{owning_lib};
16825 } else {
16826     # are.owner and bre.owner can be null, so fall back to the consortial setting
16827     $owner = $_TD->{new}{owner} || 1;
16828 }
16829
16830 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
16831 if ($ous_rv->{processed}) {
16832     $ou_cni = $ous_rv->{rows}[0]->{value};
16833     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
16834 } else {
16835     # Fall back to the shortname of the OU if there was no OU setting
16836     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
16837     if ($ous_rv->{processed}) {
16838         $ou_cni = $ous_rv->{rows}[0]->{shortname};
16839     }
16840 }
16841
16842 my ($create, $munge) = (0, 0);
16843 my ($orig_001, $orig_003) = ('', '');
16844
16845 # Incoming MARC records may have multiple 001s or 003s, despite the spec
16846 my @control_ids = $record->field('003');
16847 my @scns = $record->field('035');
16848
16849 foreach my $id_field ('001', '003') {
16850     my $spec_value;
16851     my @controls = $record->field($id_field);
16852
16853     if ($id_field eq '001') {
16854         $spec_value = $rec_id;
16855     } else {
16856         $spec_value = $ou_cni;
16857     }
16858
16859     # Create the 001/003 if none exist
16860     if (scalar(@controls) == 0) {
16861         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
16862         $create = 1;
16863     } elsif (scalar(@controls) > 1) {
16864         # Do we already have the right 001/003 value in the existing set?
16865         unless (grep $_->data() eq $spec_value, @controls) {
16866             $munge = 1;
16867         }
16868
16869         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
16870         foreach my $control (@controls) {
16871             unless ($control->data() eq $spec_value) {
16872                 $record->delete_field($control);
16873             }
16874         }
16875     } else {
16876         # Only one field; check to see if we need to munge it
16877         unless (grep $_->data() eq $spec_value, @controls) {
16878             $munge = 1;
16879         }
16880     }
16881 }
16882
16883 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
16884 if ($munge) {
16885     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
16886
16887     # Do not create duplicate 035 fields
16888     unless (grep $_->subfield('a') eq $scn, @scns) {
16889         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
16890     }
16891 }
16892
16893 # Set the 001/003 and update the MARC
16894 if ($create or $munge) {
16895     $record->field('001')->data($rec_id);
16896     $record->field('003')->data($ou_cni);
16897
16898     my $xml = $record->as_xml_record();
16899     $xml =~ s/\n//sgo;
16900     $xml =~ s/^<\?xml.+\?\s*>//go;
16901     $xml =~ s/>\s+</></go;
16902     $xml =~ s/\p{Cc}//go;
16903
16904     # Embed a version of OpenILS::Application::AppUtils->entityize()
16905     # to avoid having to set PERL5LIB for PostgreSQL as well
16906
16907     # If we are going to convert non-ASCII characters to XML entities,
16908     # we had better be dealing with a UTF8 string to begin with
16909     $xml = decode_utf8($xml);
16910
16911     $xml = NFC($xml);
16912
16913     # Convert raw ampersands to entities
16914     $xml =~ s/&(?!\S+;)/&amp;/gso;
16915
16916     # Convert Unicode characters to entities
16917     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
16918
16919     $xml =~ s/[\x00-\x1f]//go;
16920     $_TD->{new}{marc} = $xml;
16921
16922     return "MODIFY";
16923 }
16924
16925 return;
16926 $func$ LANGUAGE PLPERLU;
16927
16928 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16929 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16930 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16931
16932 INSERT INTO metabib.facet_entry (source, field, value)
16933     SELECT source, field, value FROM (
16934         SELECT * FROM metabib.author_field_entry
16935             UNION ALL
16936         SELECT * FROM metabib.keyword_field_entry
16937             UNION ALL
16938         SELECT * FROM metabib.identifier_field_entry
16939             UNION ALL
16940         SELECT * FROM metabib.title_field_entry
16941             UNION ALL
16942         SELECT * FROM metabib.subject_field_entry
16943             UNION ALL
16944         SELECT * FROM metabib.series_field_entry
16945         )x
16946     WHERE x.index_vector = '';
16947         
16948 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
16949 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
16950 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
16951 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
16952 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
16953 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
16954
16955 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
16956 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
16957 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
16958
16959 -- copy OPAC visibility materialized view
16960 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
16961
16962     TRUNCATE TABLE asset.opac_visible_copies;
16963
16964     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16965     SELECT  cp.id, cp.circ_lib, cn.record
16966     FROM  asset.copy cp
16967         JOIN asset.call_number cn ON (cn.id = cp.call_number)
16968         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
16969         JOIN asset.copy_location cl ON (cp.location = cl.id)
16970         JOIN config.copy_status cs ON (cp.status = cs.id)
16971         JOIN biblio.record_entry b ON (cn.record = b.id)
16972     WHERE NOT cp.deleted
16973         AND NOT cn.deleted
16974         AND NOT b.deleted
16975         AND cs.opac_visible
16976         AND cl.opac_visible
16977         AND cp.opac_visible
16978         AND a.opac_visible;
16979
16980 $$ LANGUAGE SQL;
16981 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
16982 Rebuild the copy OPAC visibility cache.  Useful during migrations.
16983 $$;
16984
16985 -- and actually populate the table
16986 SELECT asset.refresh_opac_visible_copies_mat_view();
16987
16988 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
16989 DECLARE
16990     add_query       TEXT;
16991     remove_query    TEXT;
16992     do_add          BOOLEAN := false;
16993     do_remove       BOOLEAN := false;
16994 BEGIN
16995     add_query := $$
16996             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16997                 SELECT  cp.id, cp.circ_lib, cn.record
16998                   FROM  asset.copy cp
16999                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17000                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17001                         JOIN asset.copy_location cl ON (cp.location = cl.id)
17002                         JOIN config.copy_status cs ON (cp.status = cs.id)
17003                         JOIN biblio.record_entry b ON (cn.record = b.id)
17004                   WHERE NOT cp.deleted
17005                         AND NOT cn.deleted
17006                         AND NOT b.deleted
17007                         AND cs.opac_visible
17008                         AND cl.opac_visible
17009                         AND cp.opac_visible
17010                         AND a.opac_visible
17011     $$;
17012  
17013     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
17014
17015     IF TG_OP = 'INSERT' THEN
17016
17017         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17018             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17019             EXECUTE add_query;
17020         END IF;
17021
17022         RETURN NEW;
17023
17024     END IF;
17025
17026     -- handle items first, since with circulation activity
17027     -- their statuses change frequently
17028     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17029
17030         IF OLD.location    <> NEW.location OR
17031            OLD.call_number <> NEW.call_number OR
17032            OLD.status      <> NEW.status OR
17033            OLD.circ_lib    <> NEW.circ_lib THEN
17034             -- any of these could change visibility, but
17035             -- we'll save some queries and not try to calculate
17036             -- the change directly
17037             do_remove := true;
17038             do_add := true;
17039         ELSE
17040
17041             IF OLD.deleted <> NEW.deleted THEN
17042                 IF NEW.deleted THEN
17043                     do_remove := true;
17044                 ELSE
17045                     do_add := true;
17046                 END IF;
17047             END IF;
17048
17049             IF OLD.opac_visible <> NEW.opac_visible THEN
17050                 IF OLD.opac_visible THEN
17051                     do_remove := true;
17052                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
17053                                         -- is also marked opac_visible
17054                     do_add := true;
17055                 END IF;
17056             END IF;
17057
17058         END IF;
17059
17060         IF do_remove THEN
17061             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
17062         END IF;
17063         IF do_add THEN
17064             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17065             EXECUTE add_query;
17066         END IF;
17067
17068         RETURN NEW;
17069
17070     END IF;
17071
17072     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
17073  
17074         IF OLD.deleted AND NEW.deleted THEN -- do nothing
17075
17076             RETURN NEW;
17077  
17078         ELSIF NEW.deleted THEN -- remove rows
17079  
17080             IF TG_TABLE_NAME = 'call_number' THEN
17081                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
17082             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17083                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
17084             END IF;
17085  
17086             RETURN NEW;
17087  
17088         ELSIF OLD.deleted THEN -- add rows
17089  
17090             IF TG_TABLE_NAME IN ('copy','unit') THEN
17091                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17092             ELSIF TG_TABLE_NAME = 'call_number' THEN
17093                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17094             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17095                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
17096             END IF;
17097  
17098             EXECUTE add_query;
17099             RETURN NEW;
17100  
17101         END IF;
17102  
17103     END IF;
17104
17105     IF TG_TABLE_NAME = 'call_number' THEN
17106
17107         IF OLD.record <> NEW.record THEN
17108             -- call number is linked to different bib
17109             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
17110             EXECUTE remove_query;
17111             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17112             EXECUTE add_query;
17113         END IF;
17114
17115         RETURN NEW;
17116
17117     END IF;
17118
17119     IF TG_TABLE_NAME IN ('record_entry') THEN
17120         RETURN NEW; -- don't have 'opac_visible'
17121     END IF;
17122
17123     -- actor.org_unit, asset.copy_location, asset.copy_status
17124     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
17125
17126         RETURN NEW;
17127
17128     ELSIF NEW.opac_visible THEN -- add rows
17129
17130         IF TG_TABLE_NAME = 'org_unit' THEN
17131             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
17132         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17133             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
17134         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17135             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
17136         END IF;
17137  
17138         EXECUTE add_query;
17139  
17140     ELSE -- delete rows
17141
17142         IF TG_TABLE_NAME = 'org_unit' THEN
17143             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17144         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17145             remove_query := remove_query || 'location = ' || NEW.id || ');';
17146         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17147             remove_query := remove_query || 'status = ' || NEW.id || ');';
17148         END IF;
17149  
17150         EXECUTE remove_query;
17151  
17152     END IF;
17153  
17154     RETURN NEW;
17155 END;
17156 $func$ LANGUAGE PLPGSQL;
17157 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17158 Trigger function to update the copy OPAC visiblity cache.
17159 $$;
17160 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();
17161 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17162 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();
17163 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();
17164 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17165 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();
17166 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();
17167
17168 -- must create this rule explicitly; it is not inherited from asset.copy
17169 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;
17170
17171 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);
17172
17173 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17174 DECLARE
17175     moved_objects INT := 0;
17176     bib_id        INT := 0;
17177     bib_rec       biblio.record_entry%ROWTYPE;
17178     auth_link     authority.bib_linking%ROWTYPE;
17179 BEGIN
17180
17181     -- 1. Make source_record MARC a copy of the target_record to get auto-sync in linked bib records
17182     UPDATE authority.record_entry
17183       SET marc = (
17184         SELECT marc
17185           FROM authority.record_entry
17186           WHERE id = target_record
17187       )
17188       WHERE id = source_record;
17189
17190     -- 2. Update all bib records with the ID from target_record in their $0
17191     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre 
17192       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17193       WHERE abl.authority = target_record LOOP
17194
17195         UPDATE biblio.record_entry
17196           SET marc = REGEXP_REPLACE(marc, 
17197             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17198             E'\\1' || target_record || '<', 'g')
17199           WHERE id = bib_rec.id;
17200
17201           moved_objects := moved_objects + 1;
17202     END LOOP;
17203
17204     -- 3. "Delete" source_record
17205     DELETE FROM authority.record_entry
17206       WHERE id = source_record;
17207
17208     RETURN moved_objects;
17209 END;
17210 $func$ LANGUAGE plpgsql;
17211
17212 -- serial.record_entry already had an owner column spelled "owning_lib"
17213 -- Adjust the table and affected functions accordingly
17214
17215 ALTER TABLE serial.record_entry DROP COLUMN owner;
17216
17217 CREATE TABLE actor.usr_saved_search (
17218     id              SERIAL          PRIMARY KEY,
17219         owner           INT             NOT NULL REFERENCES actor.usr (id)
17220                                         ON DELETE CASCADE
17221                                         DEFERRABLE INITIALLY DEFERRED,
17222         name            TEXT            NOT NULL,
17223         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17224         query_text      TEXT            NOT NULL,
17225         query_type      TEXT            NOT NULL
17226                                         CONSTRAINT valid_query_text CHECK (
17227                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17228                                         -- we may add other types someday
17229         target          TEXT            NOT NULL
17230                                         CONSTRAINT valid_target CHECK (
17231                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17232         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17233 );
17234
17235 -- Apply Dan Wells' changes to the serial schema, from the
17236 -- seials-integration branch
17237
17238 CREATE TABLE serial.subscription_note (
17239         id           SERIAL PRIMARY KEY,
17240         subscription INT    NOT NULL
17241                             REFERENCES serial.subscription (id)
17242                             ON DELETE CASCADE
17243                             DEFERRABLE INITIALLY DEFERRED,
17244         creator      INT    NOT NULL
17245                             REFERENCES actor.usr (id)
17246                             DEFERRABLE INITIALLY DEFERRED,
17247         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17248         pub          BOOL   NOT NULL DEFAULT FALSE,
17249         title        TEXT   NOT NULL,
17250         value        TEXT   NOT NULL
17251 );
17252 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17253
17254 CREATE TABLE serial.distribution_note (
17255         id           SERIAL PRIMARY KEY,
17256         distribution INT    NOT NULL
17257                             REFERENCES serial.distribution (id)
17258                             ON DELETE CASCADE
17259                             DEFERRABLE INITIALLY DEFERRED,
17260         creator      INT    NOT NULL
17261                             REFERENCES actor.usr (id)
17262                             DEFERRABLE INITIALLY DEFERRED,
17263         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17264         pub          BOOL   NOT NULL DEFAULT FALSE,
17265         title        TEXT   NOT NULL,
17266         value        TEXT   NOT NULL
17267 );
17268 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17269
17270 ------- Begin surgery on serial.unit
17271
17272 ALTER TABLE serial.unit
17273         DROP COLUMN label;
17274
17275 ALTER TABLE serial.unit
17276         RENAME COLUMN label_sort_key TO sort_key;
17277
17278 ALTER TABLE serial.unit
17279         RENAME COLUMN contents TO detailed_contents;
17280
17281 ALTER TABLE serial.unit
17282         ADD COLUMN summary_contents TEXT;
17283
17284 UPDATE serial.unit
17285 SET summary_contents = detailed_contents;
17286
17287 ALTER TABLE serial.unit
17288         ALTER column summary_contents SET NOT NULL;
17289
17290 ------- End surgery on serial.unit
17291
17292 -- 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' );
17293
17294 -- Now rebuild the constraints dropped via cascade.
17295 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17296 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17297 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17298
17299 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17300
17301 DELETE FROM config.metabib_field_index_norm_map
17302     WHERE norm IN (
17303         SELECT id 
17304             FROM config.index_normalizer
17305             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17306     )
17307     AND field = 18
17308 ;
17309
17310 -- We won't necessarily use all of these, but they are here for completeness.
17311 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17312 -- Values are the EDI code value + 1200
17313
17314 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17315 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17316 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17317 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17318 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17319 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17320 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17321 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17322 (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.'),
17323 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17324 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17325 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17326 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17327 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17328 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17329 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17330 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17331 (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.'),
17332 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17333 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17334 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17335 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17336 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17337 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17338 (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.'),
17339 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17340 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17341 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17342 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17343 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17344 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17345 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17346 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17347 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17348 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17349 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17350 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17351 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17352 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17353 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17354 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17355 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17356 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17357 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17358 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17359 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17360 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17361 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17362 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17363 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17364 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17365 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17366 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17367 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17368 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17369 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17370 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17371 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17372 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17373 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17374 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17375 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17376 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17377 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17378 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17379 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17380 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17381 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17382 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17383 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17384 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17385 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17386 (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.'),
17387 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17388 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17389 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17390 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17391 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17392 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17393 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17394 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17395 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17396 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17397 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17398 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17399 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17400 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17401 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17402 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17403 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17404 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17405 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17406 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17407 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17408 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17409 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17410 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17411 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17412 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17413 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17414 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17415 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17416 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17417 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17418 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17419 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17420 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17421 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17422 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17423 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17424 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17425 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17426 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17427 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17428 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17429 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17430 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17431 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17432 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17433 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17434 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17435 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17436 (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.'),
17437 (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.'),
17438 (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.'),
17439 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17440 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17441 (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.'),
17442 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17443 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17444 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17445 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17446 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17447 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17448 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17449 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17450 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17451 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17452 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17453 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17454 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17455 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17456 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17457 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17458 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17459 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17460 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17461 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17462 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17463 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17464 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17465 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17466 (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.'),
17467 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17468 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17469 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17470 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17471 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17472 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17473 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17474 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17475 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17476 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17477 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17478 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17479 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17480 (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.'),
17481 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17482 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17483 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17484 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17485 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17486 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17487 (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.'),
17488 (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.'),
17489 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17490 (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.'),
17491 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17492 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17493 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17494 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17495 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17496 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17497 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17498 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17499 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17500 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17501 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17502 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17503 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17504 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17505 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17506 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17507 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17508 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17509 (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.'),
17510 (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.'),
17511 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17512 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17513 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17514 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17515 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17516 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17517 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17518 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17519 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17520 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17521 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17522 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17523 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17524 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17525 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17526 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17527 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17528 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17529 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17530 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17531 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17532 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17533 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17534 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17535 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17536 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17537 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17538 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17539 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17540 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17541 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17542 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17543 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17544 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17545 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17546 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17547 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17548 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17549 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17550 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17551 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17552 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17553 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17554 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17555 (1, 't', 1442, 'Number of months', 'The number of months.'),
17556 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17557 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17558 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17559 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17560 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17561 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17562 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17563 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17564 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17565 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17566 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17567 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17568 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17569 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17570 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17571 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17572 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17573 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17574 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17575 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17576 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17577 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17578 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17579 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17580 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17581 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17582 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17583 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17584 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17585 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17586 (1, 't', 1473, 'Agents', 'The number of agents.'),
17587 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17588 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17589 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17590 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17591 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17592 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17593 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17594 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17595 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17596 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17597 (1, 't', 1484, 'Departments', 'The number of departments.'),
17598 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17599 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17600 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17601 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17602 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17603 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17604 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17605 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17606 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17607 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17608 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17609 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17610 (1, 't', 1497, 'Executives', 'The number of executives.'),
17611 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17612 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17613 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17614 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17615 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17616 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17617 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17618 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17619 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17620 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17621 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17622 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17623 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17624 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17625 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17626 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17627 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17628 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17629 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17630 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17631 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17632 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17633 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17634 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17635 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17636 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17637 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17638 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17639 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17640 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17641 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17642 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17643 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17644 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17645 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17646 (1, 't', 1533, 'Seats',        'The number of seats.'),
17647 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17648 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17649 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17650 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17651 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17652 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17653 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17654 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17655 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17656 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17657 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17658 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17659 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17660 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17661 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17662 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17663 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17664 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17665 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17666 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17667 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17668 (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.'),
17669 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17670 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17671 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17672 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17673 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17674 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17675 (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.'),
17676 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17677 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17678 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17679 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17680 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17681 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17682 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17683 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17684 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17685 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17686 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17687 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17688 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17689 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17690 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17691 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17692 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17693 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17694 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17695 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17696 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17697 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17698 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17699 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17700 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17701 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17702 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17703 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17704 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17705 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17706 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17707 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17708 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17709 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17710 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17711 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17712 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17713 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17714 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17715 (1, 't', 1602, 'Patients',         'Number of patients.'),
17716 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17717 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17718 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17719 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17720 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17721 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17722 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17723 (1, 't', 1610, 'Operators',        'Number of operators.'),
17724 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17725 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17726 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17727 (1, 't', 1614, 'Machines',         'Number of machines.'),
17728 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17729 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17730 (1, 't', 1617, 'Directors',        'Number of directors.'),
17731 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17732 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17733 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17734 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17735 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17736 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17737 (1, 't', 1624, 'Beds', 'Number of beds.'),
17738 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17739 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17740 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17741 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
17742 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
17743 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
17744 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
17745 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
17746 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
17747 (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.'),
17748 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
17749 (1, 't', 1636, 'Professor', 'The number of professors.'),
17750 (1, 't', 1637, 'Seller',    'The number of sellers.'),
17751 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
17752 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
17753 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
17754 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
17755 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
17756 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
17757 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
17758 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
17759 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
17760 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
17761 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
17762 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
17763 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
17764 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
17765 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
17766 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
17767 (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.'),
17768 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
17769 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
17770 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
17771 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
17772 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
17773 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
17774 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
17775 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
17776 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
17777 (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.'),
17778 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
17779 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
17780 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
17781 (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.'),
17782 (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.'),
17783 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
17784 (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.'),
17785 (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.'),
17786 (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.'),
17787 (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.'),
17788 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
17789 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
17790 (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.'),
17791 (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.'),
17792 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
17793 (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.'),
17794 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
17795 (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.'),
17796 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
17797 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
17798 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
17799 (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).'),
17800 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
17801 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
17802 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
17803 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
17804 ;
17805 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
17806
17807 CREATE TABLE acq.serial_claim (
17808     id     SERIAL           PRIMARY KEY,
17809     type   INT              NOT NULL REFERENCES acq.claim_type
17810                                      DEFERRABLE INITIALLY DEFERRED,
17811     item    BIGINT          NOT NULL REFERENCES serial.item
17812                                      DEFERRABLE INITIALLY DEFERRED
17813 );
17814
17815 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
17816
17817 CREATE TABLE acq.serial_claim_event (
17818     id             BIGSERIAL        PRIMARY KEY,
17819     type           INT              NOT NULL REFERENCES acq.claim_event_type
17820                                              DEFERRABLE INITIALLY DEFERRED,
17821     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
17822                                              DEFERRABLE INITIALLY DEFERRED,
17823     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
17824     creator        INT              NOT NULL REFERENCES actor.usr
17825                                              DEFERRABLE INITIALLY DEFERRED,
17826     note           TEXT
17827 );
17828
17829 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
17830
17831 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
17832
17833 -- now what about the auditor.*_lifecycle views??
17834
17835 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17836     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
17837 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17838     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
17839 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
17840 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
17841
17842 CREATE TABLE asset.call_number_class (
17843     id             bigserial     PRIMARY KEY,
17844     name           TEXT          NOT NULL,
17845     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
17846     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17847 );
17848
17849 COMMENT ON TABLE asset.call_number_class IS $$
17850 Defines the call number normalization database functions in the "normalizer"
17851 column and the tag/subfield combinations to use to lookup the call number in
17852 the "field" column for a given classification scheme. Tag/subfield combinations
17853 are delimited by commas.
17854 $$;
17855
17856 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
17857     ('Generic', 'asset.label_normalizer_generic'),
17858     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
17859     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
17860 ;
17861
17862 -- Generic fields
17863 UPDATE asset.call_number_class
17864     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17865     WHERE id = 1
17866 ;
17867
17868 -- Dewey fields
17869 UPDATE asset.call_number_class
17870     SET field = '080ab,082ab'
17871     WHERE id = 2
17872 ;
17873
17874 -- LC fields
17875 UPDATE asset.call_number_class
17876     SET field = '050ab,055ab'
17877     WHERE id = 3
17878 ;
17879  
17880 ALTER TABLE asset.call_number
17881         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
17882                 REFERENCES asset.call_number_class(id)
17883                 DEFERRABLE INITIALLY DEFERRED;
17884
17885 ALTER TABLE asset.call_number
17886         ADD COLUMN label_sortkey TEXT;
17887
17888 CREATE INDEX asset_call_number_label_sortkey
17889         ON asset.call_number(oils_text_as_bytea(label_sortkey));
17890
17891 ALTER TABLE auditor.asset_call_number_history
17892         ADD COLUMN label_class BIGINT;
17893
17894 ALTER TABLE auditor.asset_call_number_history
17895         ADD COLUMN label_sortkey TEXT;
17896
17897 -- Pick up the new columns in dependent views
17898
17899 DROP VIEW auditor.asset_call_number_lifecycle;
17900
17901 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17902
17903 DROP VIEW auditor.asset_call_number_lifecycle;
17904
17905 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17906
17907 DROP VIEW IF EXISTS stats.fleshed_call_number;
17908
17909 CREATE VIEW stats.fleshed_call_number AS
17910         SELECT  cn.*,
17911             CAST(cn.create_date AS DATE) AS create_date_day,
17912         CAST(cn.edit_date AS DATE) AS edit_date_day,
17913         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
17914         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
17915             rd.item_lang,
17916                 rd.item_type,
17917                 rd.item_form
17918         FROM    asset.call_number cn
17919                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
17920
17921 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
17922 DECLARE
17923     sortkey        TEXT := '';
17924 BEGIN
17925     sortkey := NEW.label_sortkey;
17926
17927     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
17928        quote_literal( NEW.label ) || ')'
17929        FROM asset.call_number_class acnc
17930        WHERE acnc.id = NEW.label_class
17931        INTO sortkey;
17932
17933     NEW.label_sortkey = sortkey;
17934
17935     RETURN NEW;
17936 END;
17937 $func$ LANGUAGE PLPGSQL;
17938
17939 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
17940     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
17941     # thus could probably be considered a derived work, although nothing was
17942     # directly copied - but to err on the safe side of providing attribution:
17943     # Copyright (C) 2007 LibLime
17944     # Licensed under the GPL v2 or later
17945
17946     use strict;
17947     use warnings;
17948
17949     # Converts the callnumber to uppercase
17950     # Strips spaces from start and end of the call number
17951     # Converts anything other than letters, digits, and periods into underscores
17952     # Collapses multiple underscores into a single underscore
17953     my $callnum = uc(shift);
17954     $callnum =~ s/^\s//g;
17955     $callnum =~ s/\s$//g;
17956     $callnum =~ s/[^A-Z0-9_.]/_/g;
17957     $callnum =~ s/_{2,}/_/g;
17958
17959     return $callnum;
17960 $func$ LANGUAGE PLPERLU;
17961
17962 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
17963     # Derived from the Koha C4::ClassSortRoutine::Dewey module
17964     # Copyright (C) 2007 LibLime
17965     # Licensed under the GPL v2 or later
17966
17967     use strict;
17968     use warnings;
17969
17970     my $init = uc(shift);
17971     $init =~ s/^\s+//;
17972     $init =~ s/\s+$//;
17973     $init =~ s!/!!g;
17974     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
17975     my @tokens = split /\.|\s+/, $init;
17976     my $digit_group_count = 0;
17977     for (my $i = 0; $i <= $#tokens; $i++) {
17978         if ($tokens[$i] =~ /^\d+$/) {
17979             $digit_group_count++;
17980             if (2 == $digit_group_count) {
17981                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
17982                 $tokens[$i] =~ tr/ /0/;
17983             }
17984         }
17985     }
17986     my $key = join("_", @tokens);
17987     $key =~ s/[^\p{IsAlnum}_]//g;
17988
17989     return $key;
17990
17991 $func$ LANGUAGE PLPERLU;
17992
17993 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
17994     use strict;
17995     use warnings;
17996
17997     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
17998     # The author hopes to upload it to CPAN some day, which would make our lives easier
17999     use Library::CallNumber::LC;
18000
18001     my $callnum = Library::CallNumber::LC->new(shift);
18002     return $callnum->normalize();
18003
18004 $func$ LANGUAGE PLPERLU;
18005
18006 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$
18007 DECLARE
18008     ans RECORD;
18009     trans INT;
18010 BEGIN
18011     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;
18012
18013     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
18014         RETURN QUERY
18015         SELECT  ans.depth,
18016                 ans.id,
18017                 COUNT( av.id ),
18018                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18019                 COUNT( av.id ),
18020                 trans
18021           FROM
18022                 actor.org_unit_descendants(ans.id) d
18023                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18024                 JOIN asset.copy cp ON (cp.id = av.id)
18025           GROUP BY 1,2,6;
18026
18027         IF NOT FOUND THEN
18028             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18029         END IF;
18030
18031     END LOOP;
18032
18033     RETURN;
18034 END;
18035 $f$ LANGUAGE PLPGSQL;
18036
18037 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$
18038 DECLARE
18039     ans RECORD;
18040     trans INT;
18041 BEGIN
18042     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;
18043
18044     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18045         RETURN QUERY
18046         SELECT  -1,
18047                 ans.id,
18048                 COUNT( av.id ),
18049                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18050                 COUNT( av.id ),
18051                 trans
18052           FROM
18053                 actor.org_unit_descendants(ans.id) d
18054                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18055                 JOIN asset.copy cp ON (cp.id = av.id)
18056           GROUP BY 1,2,6;
18057
18058         IF NOT FOUND THEN
18059             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18060         END IF;
18061
18062     END LOOP;
18063
18064     RETURN;
18065 END;
18066 $f$ LANGUAGE PLPGSQL;
18067
18068 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$
18069 DECLARE
18070     ans RECORD;
18071     trans INT;
18072 BEGIN
18073     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;
18074
18075     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
18076         RETURN QUERY
18077         SELECT  ans.depth,
18078                 ans.id,
18079                 COUNT( cp.id ),
18080                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18081                 COUNT( cp.id ),
18082                 trans
18083           FROM
18084                 actor.org_unit_descendants(ans.id) d
18085                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18086                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18087           GROUP BY 1,2,6;
18088
18089         IF NOT FOUND THEN
18090             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18091         END IF;
18092
18093     END LOOP;
18094
18095     RETURN;
18096 END;
18097 $f$ LANGUAGE PLPGSQL;
18098
18099 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$
18100 DECLARE
18101     ans RECORD;
18102     trans INT;
18103 BEGIN
18104     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;
18105
18106     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18107         RETURN QUERY
18108         SELECT  -1,
18109                 ans.id,
18110                 COUNT( cp.id ),
18111                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18112                 COUNT( cp.id ),
18113                 trans
18114           FROM
18115                 actor.org_unit_descendants(ans.id) d
18116                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18117                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18118           GROUP BY 1,2,6;
18119
18120         IF NOT FOUND THEN
18121             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18122         END IF;
18123
18124     END LOOP;
18125
18126     RETURN;
18127 END;
18128 $f$ LANGUAGE PLPGSQL;
18129
18130 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$
18131 BEGIN
18132     IF staff IS TRUE THEN
18133         IF place > 0 THEN
18134             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
18135         ELSE
18136             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
18137         END IF;
18138     ELSE
18139         IF place > 0 THEN
18140             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18141         ELSE
18142             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18143         END IF;
18144     END IF;
18145
18146     RETURN;
18147 END;
18148 $f$ LANGUAGE PLPGSQL;
18149
18150 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$
18151 DECLARE
18152     ans RECORD;
18153     trans INT;
18154 BEGIN
18155     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;
18156
18157     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
18158         RETURN QUERY
18159         SELECT  ans.depth,
18160                 ans.id,
18161                 COUNT( av.id ),
18162                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18163                 COUNT( av.id ),
18164                 trans
18165           FROM
18166                 actor.org_unit_descendants(ans.id) d
18167                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18168                 JOIN asset.copy cp ON (cp.id = av.id)
18169                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18170           GROUP BY 1,2,6;
18171
18172         IF NOT FOUND THEN
18173             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18174         END IF;
18175
18176     END LOOP;
18177
18178     RETURN;
18179 END;
18180 $f$ LANGUAGE PLPGSQL;
18181
18182 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$
18183 DECLARE
18184     ans RECORD;
18185     trans INT;
18186 BEGIN
18187     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;
18188
18189     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18190         RETURN QUERY
18191         SELECT  -1,
18192                 ans.id,
18193                 COUNT( av.id ),
18194                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18195                 COUNT( av.id ),
18196                 trans
18197           FROM
18198                 actor.org_unit_descendants(ans.id) d
18199                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18200                 JOIN asset.copy cp ON (cp.id = av.id)
18201                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18202           GROUP BY 1,2,6;
18203
18204         IF NOT FOUND THEN
18205             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18206         END IF;
18207
18208     END LOOP;
18209
18210     RETURN;
18211 END;
18212 $f$ LANGUAGE PLPGSQL;
18213
18214 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$
18215 DECLARE
18216     ans RECORD;
18217     trans INT;
18218 BEGIN
18219     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;
18220
18221     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
18222         RETURN QUERY
18223         SELECT  ans.depth,
18224                 ans.id,
18225                 COUNT( cp.id ),
18226                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18227                 COUNT( cp.id ),
18228                 trans
18229           FROM
18230                 actor.org_unit_descendants(ans.id) d
18231                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18232                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18233                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18234           GROUP BY 1,2,6;
18235
18236         IF NOT FOUND THEN
18237             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18238         END IF;
18239
18240     END LOOP;
18241
18242     RETURN;
18243 END;
18244 $f$ LANGUAGE PLPGSQL;
18245
18246 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$
18247 DECLARE
18248     ans RECORD;
18249     trans INT;
18250 BEGIN
18251     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;
18252
18253     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18254         RETURN QUERY
18255         SELECT  -1,
18256                 ans.id,
18257                 COUNT( cp.id ),
18258                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18259                 COUNT( cp.id ),
18260                 trans
18261           FROM
18262                 actor.org_unit_descendants(ans.id) d
18263                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18264                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18265                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18266           GROUP BY 1,2,6;
18267
18268         IF NOT FOUND THEN
18269             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18270         END IF;
18271
18272     END LOOP;
18273
18274     RETURN;
18275 END;
18276 $f$ LANGUAGE PLPGSQL;
18277
18278 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$
18279 BEGIN
18280     IF staff IS TRUE THEN
18281         IF place > 0 THEN
18282             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18283         ELSE
18284             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18285         END IF;
18286     ELSE
18287         IF place > 0 THEN
18288             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18289         ELSE
18290             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18291         END IF;
18292     END IF;
18293
18294     RETURN;
18295 END;
18296 $f$ LANGUAGE PLPGSQL;
18297
18298 -- No transaction is required
18299
18300 -- Triggers on the vandelay.queued_*_record tables delete entries from
18301 -- the associated vandelay.queued_*_record_attr tables based on the record's
18302 -- ID; create an index on that column to avoid sequential scans for each
18303 -- queued record that is deleted
18304 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18305 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18306
18307 -- Avoid sequential scans for queue retrieval operations by providing an
18308 -- index on the queue column
18309 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18310 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18311
18312 -- Start picking up call number label prefixes and suffixes
18313 -- from asset.copy_location
18314 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18315 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18316
18317 DROP VIEW auditor.asset_copy_lifecycle;
18318
18319 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18320
18321 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18322
18323 -- Let's not break existing reports
18324 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18325 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18326
18327 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18328 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18329 SELECT  r.id,
18330     r.fingerprint,
18331     r.quality,
18332     r.tcn_source,
18333     r.tcn_value,
18334     FIRST(title.value) AS title,
18335     FIRST(author.value) AS author,
18336     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18337     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18338     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18339     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18340   FROM  biblio.record_entry r
18341     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18342     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18343     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18344     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18345     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18346     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18347   GROUP BY 1,2,3,4,5;
18348
18349 -- Correct the ISSN array definition for reporter.simple_record
18350
18351 CREATE OR REPLACE VIEW reporter.simple_record AS
18352 SELECT  r.id,
18353         s.metarecord,
18354         r.fingerprint,
18355         r.quality,
18356         r.tcn_source,
18357         r.tcn_value,
18358         title.value AS title,
18359         uniform_title.value AS uniform_title,
18360         author.value AS author,
18361         publisher.value AS publisher,
18362         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18363         series_title.value AS series_title,
18364         series_statement.value AS series_statement,
18365         summary.value AS summary,
18366         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18367         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18368         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18369         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18370         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18371         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18372         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18373         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
18374   FROM  biblio.record_entry r
18375         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18376         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18377         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18378         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18379         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18380         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18381         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18382         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18383         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')
18384         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18385         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18386   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18387
18388 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18389     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
18390 $$ LANGUAGE SQL;
18391
18392 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18393 BEGIN
18394     IF TG_OP = 'DELETE' THEN
18395         PERFORM reporter.simple_rec_delete(NEW.id);
18396     ELSE
18397         PERFORM reporter.simple_rec_update(NEW.id);
18398     END IF;
18399
18400     RETURN NEW;
18401 END;
18402 $func$ LANGUAGE PLPGSQL;
18403
18404 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
18405
18406 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18407
18408 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18409
18410 UPDATE config.org_unit_setting_type
18411     SET view_perm = (SELECT id FROM permission.perm_list
18412         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18413     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18414
18415 UPDATE config.org_unit_setting_type
18416     SET update_perm = (SELECT id FROM permission.perm_list
18417         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18418     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18419
18420 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18421     VALUES (
18422         'opac.fully_compressed_serial_holdings',
18423         'OPAC: Use fully compressed serial holdings',
18424         'Show fully compressed serial holdings for all libraries at and below
18425         the current context unit',
18426         'bool'
18427     );
18428
18429 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18430     use strict;
18431     use warnings;
18432
18433     use utf8;
18434     use MARC::Record;
18435     use MARC::File::XML (BinaryEncoding => 'UTF8');
18436     use UUID::Tiny ':std';
18437
18438     my $xml = shift() or return undef;
18439
18440     my $r;
18441
18442     # Prevent errors in XML parsing from blowing out ungracefully
18443     eval {
18444         $r = MARC::Record->new_from_xml( $xml );
18445         1;
18446     } or do {
18447        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18448     };
18449
18450     if (!$r) {
18451        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18452     }
18453
18454     # From http://www.loc.gov/standards/sourcelist/subject.html
18455     my $thes_code_map = {
18456         a => 'lcsh',
18457         b => 'lcshac',
18458         c => 'mesh',
18459         d => 'nal',
18460         k => 'cash',
18461         n => 'notapplicable',
18462         r => 'aat',
18463         s => 'sears',
18464         v => 'rvm',
18465     };
18466
18467     # Default to "No attempt to code" if the leader is horribly broken
18468     my $fixed_field = $r->field('008');
18469     my $thes_char = '|';
18470     if ($fixed_field) {
18471         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18472     }
18473
18474     my $thes_code = 'UNDEFINED';
18475
18476     if ($thes_char eq 'z') {
18477         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18478         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18479     } elsif ($thes_code_map->{$thes_char}) {
18480         $thes_code = $thes_code_map->{$thes_char};
18481     }
18482
18483     my $auth_txt = '';
18484     my $head = $r->field('1..');
18485     if ($head) {
18486         # Concatenate all of these subfields together, prefixed by their code
18487         # to prevent collisions along the lines of "Fiction, North Carolina"
18488         foreach my $sf ($head->subfields()) {
18489             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18490         }
18491     }
18492
18493     # Perhaps better to parameterize the spi and pass as a parameter
18494     $auth_txt =~ s/'//go;
18495
18496     if ($auth_txt) {
18497         my $result = spi_exec_query("SELECT public.naco_normalize('$auth_txt') AS norm_text");
18498         my $norm_txt = $result->{rows}[0]->{norm_text};
18499         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18500     }
18501
18502     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18503 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18504
18505 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18506 /**
18507 * Extract the authority heading, thesaurus, and NACO-normalized values
18508 * from an authority record. The primary purpose is to build a unique
18509 * index to defend against duplicated authority records from the same
18510 * thesaurus.
18511 */
18512 $$;
18513
18514 DROP INDEX authority.authority_record_unique_tcn;
18515 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18516 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18517
18518 ALTER TABLE acq.provider_contact
18519         ALTER COLUMN name SET NOT NULL;
18520
18521 ALTER TABLE actor.stat_cat
18522         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18523
18524 -- Recreate some foreign keys that were somehow dropped, probably
18525 -- by some kind of cascade from an inherited table:
18526
18527 ALTER TABLE action.reservation_transit_copy
18528         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
18529                 REFERENCES booking.resource(id)
18530                 ON DELETE CASCADE
18531                 DEFERRABLE INITIALLY DEFERRED,
18532         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
18533                 REFERENCES booking.reservation(id)
18534                 ON DELETE SET NULL
18535                 DEFERRABLE INITIALLY DEFERRED;
18536
18537 CREATE INDEX user_bucket_item_target_user_idx
18538         ON container.user_bucket_item ( target_user );
18539
18540 CREATE INDEX m_c_t_collector_idx
18541         ON money.collections_tracker ( collector );
18542
18543 CREATE INDEX aud_actor_usr_address_hist_id_idx
18544         ON auditor.actor_usr_address_history ( id );
18545
18546 CREATE INDEX aud_actor_usr_hist_id_idx
18547         ON auditor.actor_usr_history ( id );
18548
18549 CREATE INDEX aud_asset_cn_hist_creator_idx
18550         ON auditor.asset_call_number_history ( creator );
18551
18552 CREATE INDEX aud_asset_cn_hist_editor_idx
18553         ON auditor.asset_call_number_history ( editor );
18554
18555 CREATE INDEX aud_asset_cp_hist_creator_idx
18556         ON auditor.asset_copy_history ( creator );
18557
18558 CREATE INDEX aud_asset_cp_hist_editor_idx
18559         ON auditor.asset_copy_history ( editor );
18560
18561 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18562         ON auditor.biblio_record_entry_history ( creator );
18563
18564 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18565         ON auditor.biblio_record_entry_history ( editor );
18566
18567 CREATE TABLE action.hold_request_note (
18568
18569     id     BIGSERIAL PRIMARY KEY,
18570     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18571                               ON DELETE CASCADE
18572                               DEFERRABLE INITIALLY DEFERRED,
18573     title  TEXT      NOT NULL,
18574     body   TEXT      NOT NULL,
18575     slip   BOOL      NOT NULL DEFAULT FALSE,
18576     pub    BOOL      NOT NULL DEFAULT FALSE,
18577     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18578
18579 );
18580 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18581
18582 -- Tweak a constraint to add a CASCADE
18583
18584 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18585
18586 ALTER TABLE action.hold_notification
18587         ADD CONSTRAINT hold_notification_hold_fkey
18588                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18589                 ON DELETE CASCADE
18590                 DEFERRABLE INITIALLY DEFERRED;
18591
18592 CREATE TRIGGER asset_label_sortkey_trigger
18593     BEFORE UPDATE OR INSERT ON asset.call_number
18594     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18595
18596 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18597 RETURNS VOID AS $$
18598 --
18599 -- Delete expired circulation bucket items for all users that have
18600 -- a setting for patron.max_reading_list_interval.
18601 --
18602 DECLARE
18603     today        TIMESTAMP WITH TIME ZONE;
18604     threshold    TIMESTAMP WITH TIME ZONE;
18605         usr_setting  RECORD;
18606 BEGIN
18607         SELECT date_trunc( 'day', now() ) INTO today;
18608         --
18609         FOR usr_setting in
18610                 SELECT
18611                         usr,
18612                         value
18613                 FROM
18614                         actor.usr_setting
18615                 WHERE
18616                         name = 'patron.max_reading_list_interval'
18617         LOOP
18618                 --
18619                 -- Make sure the setting is a valid interval
18620                 --
18621                 BEGIN
18622                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18623                 EXCEPTION
18624                         WHEN OTHERS THEN
18625                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18626                                         usr_setting.usr, usr_setting.value;
18627                                 CONTINUE;
18628                 END;
18629                 --
18630                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18631                 --
18632         DELETE FROM container.copy_bucket_item
18633         WHERE
18634                 bucket IN
18635                 (
18636                     SELECT
18637                         id
18638                     FROM
18639                         container.copy_bucket
18640                     WHERE
18641                         owner = usr_setting.usr
18642                         AND btype = 'circ_history'
18643                 )
18644                 AND create_time < threshold;
18645         END LOOP;
18646         --
18647 END;
18648 $$ LANGUAGE plpgsql;
18649
18650 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18651 /*
18652  * Delete expired circulation bucket items for all users that have
18653  * a setting for patron.max_reading_list_interval.
18654 */
18655 $$;
18656
18657 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18658          ac_usr IN INTEGER
18659 ) RETURNS VOID AS $$
18660 --
18661 -- Delete old circulation bucket items for a specified user.
18662 -- "Old" means older than the interval specified by a
18663 -- user-level setting, if it is so specified.
18664 --
18665 DECLARE
18666     threshold TIMESTAMP WITH TIME ZONE;
18667 BEGIN
18668         -- Sanity check
18669         IF ac_usr IS NULL THEN
18670                 RETURN;
18671         END IF;
18672         -- Determine the threshold date that defines "old".  Subtract the
18673         -- interval from the system date, then truncate to midnight.
18674         SELECT
18675                 date_trunc( 
18676                         'day',
18677                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18678                 )
18679         INTO
18680                 threshold
18681         FROM
18682                 actor.usr_setting
18683         WHERE
18684                 usr = ac_usr
18685                 AND name = 'patron.max_reading_list_interval';
18686         --
18687         IF threshold is null THEN
18688                 -- No interval defined; don't delete anything
18689                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18690                 return;
18691         END IF;
18692         --
18693         -- RAISE NOTICE 'Date threshold: %', threshold;
18694         --
18695         -- Threshold found; do the delete
18696         delete from container.copy_bucket_item
18697         where
18698                 bucket in
18699                 (
18700                         select
18701                                 id
18702                         from
18703                                 container.copy_bucket
18704                         where
18705                                 owner = ac_usr
18706                                 and btype = 'circ_history'
18707                 )
18708                 and create_time < threshold;
18709         --
18710         RETURN;
18711 END;
18712 $$ LANGUAGE plpgsql;
18713
18714 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18715 /*
18716  * Delete old circulation bucket items for a specified user.
18717  * "Old" means older than the interval specified by a
18718  * user-level setting, if it is so specified.
18719 */
18720 $$;
18721
18722 CREATE OR REPLACE VIEW reporter.hold_request_record AS
18723 SELECT  id,
18724     target,
18725     hold_type,
18726     CASE
18727         WHEN hold_type = 'T'
18728             THEN target
18729         WHEN hold_type = 'I'
18730             THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
18731         WHEN hold_type = 'V'
18732             THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
18733         WHEN hold_type IN ('C','R','F')
18734             THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
18735         WHEN hold_type = 'M'
18736             THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
18737     END AS bib_record
18738   FROM  action.hold_request ahr;
18739
18740 UPDATE  metabib.rec_descriptor
18741   SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
18742         date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
18743
18744 -- Change some ints to bigints:
18745
18746 ALTER TABLE container.biblio_record_entry_bucket_item
18747         ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
18748
18749 ALTER TABLE vandelay.queued_bib_record
18750         ALTER COLUMN imported_as SET DATA TYPE bigint;
18751
18752 ALTER TABLE action.hold_copy_map
18753         ALTER COLUMN id SET DATA TYPE bigint;
18754
18755 -- Make due times get pushed to 23:59:59 on insert OR update
18756 DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
18757 CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
18758
18759 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove )
18760 SELECT 'upc', 'UPC', '//*[@tag="024" and @ind1="1"]/*[@code="a"]', $r$(?:-|\s.+$)$r$
18761 WHERE NOT EXISTS (
18762     SELECT 1 FROM acq.lineitem_marc_attr_definition WHERE code = 'upc'
18763 );
18764
18765 COMMIT;
18766
18767 -- Some operations go outside of the transaction, because they may
18768 -- legitimately fail.
18769
18770 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
18771 \qecho doesn't exist; ignore those errors if they occur.
18772
18773 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
18774
18775 ALTER TABLE auditor.action_hold_request_history
18776 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
18777
18778 ALTER TABLE auditor.action_hold_request_history
18779 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
18780
18781 \qecho Outside of the transaction: adding indexes that may or may not exist.
18782 \qecho If any of these CREATE INDEX statements fails because the index already
18783 \qecho exists, ignore the failure.
18784
18785 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
18786 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
18787 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
18788 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
18789 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
18790 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
18791 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
18792 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
18793 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
18794 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
18795 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
18796 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
18797 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
18798 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
18799 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
18800 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
18801 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
18802 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
18803 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
18804 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
18805 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
18806 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
18807 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
18808 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
18809 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
18810 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
18811 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
18812 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
18813 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
18814 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
18815 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
18816 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
18817 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
18818
18819 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
18820
18821 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
18822
18823 \qecho If the following CREATE INDEX fails, It will be necessary to do some
18824 \qecho data cleanup as described in the comments.
18825
18826 CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
18827     ON authority.record_entry (authority.normalize_heading(marc))
18828         WHERE deleted IS FALSE or deleted = FALSE;
18829
18830 -- If the unique index fails, uncomment the following to create
18831 -- a regular index that will help find the duplicates in a hurry:
18832 --CREATE INDEX by_heading_and_thesaurus
18833 --    ON authority.record_entry (authority.normalize_heading(marc))
18834 --    WHERE deleted IS FALSE or deleted = FALSE
18835 --;
18836
18837 -- Then find the duplicates like so to get an idea of how much
18838 -- pain you're looking at to clean things up:
18839 --SELECT id, authority.normalize_heading(marc)
18840 --    FROM authority.record_entry
18841 --    WHERE authority.normalize_heading(marc) IN (
18842 --        SELECT authority.normalize_heading(marc)
18843 --        FROM authority.record_entry
18844 --        GROUP BY authority.normalize_heading(marc)
18845 --        HAVING COUNT(*) > 1
18846 --    )
18847 --;
18848
18849 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
18850 -- statement succeeds, drop the temporary index to avoid unnecessary
18851 -- duplication:
18852 -- DROP INDEX authority.by_heading_and_thesaurus;
18853
18854 -- 0448.data.trigger.circ.staff_age_to_lost.sql
18855
18856 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
18857     (   'circ.staff_age_to_lost',
18858         'circ', 
18859         oils_i18n_gettext(
18860             'circ.staff_age_to_lost',
18861             'An overdue circulation should be aged to a Lost status.',
18862             'ath',
18863             'description'
18864         ), 
18865         TRUE
18866     )
18867 ;
18868
18869 INSERT INTO action_trigger.event_definition (
18870         id,
18871         active,
18872         owner,
18873         name,
18874         hook,
18875         validator,
18876         reactor,
18877         delay_field
18878     ) VALUES (
18879         36,
18880         FALSE,
18881         1,
18882         'circ.staff_age_to_lost',
18883         'circ.staff_age_to_lost',
18884         'CircIsOverdue',
18885         'MarkItemLost',
18886         'due_date'
18887     )
18888 ;
18889
18890
18891 \qecho Upgrade script completed.
18892
18893