]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
#671213: remove extraneous insert from upgrade script
[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.hook (key, core_type, description, passive)
3655     VALUES (
3656         'format.acqcle.html',
3657         'acqcle',
3658         'Formats claim events into a voucher',
3659         TRUE
3660     );
3661
3662 INSERT INTO action_trigger.event_definition (
3663         id, active, owner, name, hook, group_field,
3664         validator, reactor, granularity, template
3665     ) VALUES (
3666         21,
3667         TRUE,
3668         1,
3669         'Claim Voucher',
3670         'format.acqcle.html',
3671         'claim',
3672         'NOOP_True',
3673         'ProcessTemplate',
3674         'print-on-demand',
3675 $$
3676 [%- USE date -%]
3677 [%- SET claim = target.0.claim -%]
3678 <!-- This will need refined/prettified. -->
3679 <div class="acq-claim-voucher">
3680     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
3681     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
3682     <ul>
3683         [% FOR event IN target %]
3684         <li>
3685             Event type: [% event.type.code %]
3686             [% IF event.type.library_initiated %](Library initiated)[% END %]
3687             <br />
3688             Event date: [% event.event_date %]<br />
3689             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
3690             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
3691             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
3692             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
3693             [% event.claim.lineitem_detail.fund.code %]
3694             ([% event.claim.lineitem_detail.fund.year %])
3695         </li>
3696         [% END %]
3697     </ul>
3698 </div>
3699 $$
3700 );
3701
3702 INSERT INTO action_trigger.environment (event_def, path) VALUES
3703     (21, 'claim'),
3704     (21, 'claim.type'),
3705     (21, 'claim.lineitem_detail'),
3706     (21, 'claim.lineitem_detail.fund'),
3707     (21, 'claim.lineitem_detail.lineitem.attributes'),
3708     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
3709     (21, 'creator'),
3710     (21, 'type')
3711 ;
3712
3713 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3714     VALUES (
3715         'format.acqinv.html',
3716         'acqinv',
3717         'Formats invoices into a voucher',
3718         TRUE
3719     );
3720
3721 INSERT INTO action_trigger.event_definition (
3722         id, active, owner, name, hook,
3723         validator, reactor, granularity, template
3724     ) VALUES (
3725         22,
3726         TRUE,
3727         1,
3728         'Invoice',
3729         'format.acqinv.html',
3730         'NOOP_True',
3731         'ProcessTemplate',
3732         'print-on-demand',
3733 $$
3734 [% FILTER collapse %]
3735 [%- SET invoice = target -%]
3736 <!-- This lacks totals, info about funds (for invoice entries,
3737     funds are per-LID!), and general refinement -->
3738 <div class="acq-invoice-voucher">
3739     <h1>Invoice</h1>
3740     <div>
3741         <strong>No.</strong> [% invoice.inv_ident %]
3742         [% IF invoice.inv_type %]
3743             / <strong>Type:</strong>[% invoice.inv_type %]
3744         [% END %]
3745     </div>
3746     <div>
3747         <dl>
3748             [% BLOCK ent_with_address %]
3749             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3750             <dd>
3751                 [% IF ent.addresses.0 %]
3752                     [% SET addr = ent.addresses.0 %]
3753                     [% addr.street1 %]<br />
3754                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3755                     [% addr.city %],
3756                     [% IF addr.county %] [% addr.county %], [% END %]
3757                     [% IF addr.state %] [% addr.state %] [% END %]
3758                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3759                     [% IF addr.country %] [% addr.country %] [% END %]
3760                 [% END %]
3761                 <p>
3762                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3763                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3764                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3765                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3766                 </p>
3767             </dd>
3768             [% END %]
3769             [% INCLUDE ent_with_address
3770                 ent = invoice.provider
3771                 ent_label = "Provider" %]
3772             [% INCLUDE ent_with_address
3773                 ent = invoice.shipper
3774                 ent_label = "Shipper" %]
3775             <dt>Receiver</dt>
3776             <dd>
3777                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3778             </dd>
3779             <dt>Received</dt>
3780             <dd>
3781                 [% helpers.format_date(invoice.recv_date) %] by
3782                 [% invoice.recv_method %]
3783             </dd>
3784             [% IF invoice.note %]
3785                 <dt>Note</dt>
3786                 <dd>
3787                     [% invoice.note %]
3788                 </dd>
3789             [% END %]
3790         </dl>
3791     </div>
3792     <ul>
3793         [% FOR entry IN invoice.entries %]
3794             <li>
3795                 [% IF entry.lineitem %]
3796                     Title: [% helpers.get_li_attr(
3797                         "title", "", entry.lineitem.attributes
3798                     ) %]<br />
3799                     Author: [% helpers.get_li_attr(
3800                         "author", "", entry.lineitem.attributes
3801                     ) %]
3802                 [% END %]
3803                 [% IF entry.purchase_order %]
3804                     (PO: [% entry.purchase_order.name %])
3805                 [% END %]<br />
3806                 Invoice item count: [% entry.inv_item_count %]
3807                 [% IF entry.phys_item_count %]
3808                     / Physical item count: [% entry.phys_item_count %]
3809                 [% END %]
3810                 <br />
3811                 [% IF entry.cost_billed %]
3812                     Cost billed: [% entry.cost_billed %]
3813                     [% IF entry.billed_per_item %](per item)[% END %]
3814                     <br />
3815                 [% END %]
3816                 [% IF entry.actual_cost %]
3817                     Actual cost: [% entry.actual_cost %]<br />
3818                 [% END %]
3819                 [% IF entry.amount_paid %]
3820                     Amount paid: [% entry.amount_paid %]<br />
3821                 [% END %]
3822                 [% IF entry.note %]Note: [% entry.note %][% END %]
3823             </li>
3824         [% END %]
3825         [% FOR item IN invoice.items %]
3826             <li>
3827                 [% IF item.inv_item_type %]
3828                     Item Type: [% item.inv_item_type %]<br />
3829                 [% END %]
3830                 [% IF item.title %]Title/Description:
3831                     [% item.title %]<br />
3832                 [% END %]
3833                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3834                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3835                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3836                 [% IF item.cost_billed %]
3837                     Cost billed: [% item.cost_billed %]<br />
3838                 [% END %]
3839                 [% IF item.actual_cost %]
3840                     Actual cost: [% item.actual_cost %]<br />
3841                 [% END %]
3842                 [% IF item.amount_paid %]
3843                     Amount paid: [% item.amount_paid %]<br />
3844                 [% END %]
3845             </li>
3846         [% END %]
3847     </ul>
3848 </div>
3849 [% END %]
3850 $$
3851 );
3852
3853 INSERT INTO action_trigger.environment (event_def, path) VALUES
3854     (22, 'provider'),
3855     (22, 'provider.addresses'),
3856     (22, 'shipper'),
3857     (22, 'shipper.addresses'),
3858     (22, 'receiver'),
3859     (22, 'entries'),
3860     (22, 'entries.purchase_order'),
3861     (22, 'entries.lineitem'),
3862     (22, 'entries.lineitem.attributes'),
3863     (22, 'items')
3864 ;
3865
3866 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3867   (23, 'provider.edi_default');
3868
3869 INSERT INTO action_trigger.validator (module, description) 
3870     VALUES (
3871         'Acq::PurchaseOrderEDIRequired',
3872         oils_i18n_gettext(
3873             'Acq::PurchaseOrderEDIRequired',
3874             'Purchase order is delivered via EDI',
3875             'atval',
3876             'description'
3877         )
3878     );
3879
3880 INSERT INTO action_trigger.reactor (module, description)
3881     VALUES (
3882         'GeneratePurchaseOrderJEDI',
3883         oils_i18n_gettext(
3884             'GeneratePurchaseOrderJEDI',
3885             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
3886             'atreact',
3887             'description'
3888         )
3889     );
3890
3891 UPDATE action_trigger.hook 
3892     SET 
3893         key = 'acqpo.activated', 
3894         passive = FALSE,
3895         description = oils_i18n_gettext(
3896             'acqpo.activated',
3897             'Purchase order was activated',
3898             'ath',
3899             'description'
3900         )
3901     WHERE key = 'format.po.jedi';
3902
3903 -- We just changed a key in action_trigger.hook.  Now redirect any
3904 -- child rows to point to the new key.  (There probably aren't any;
3905 -- this is just a precaution against possible local modifications.)
3906
3907 UPDATE action_trigger.event_definition
3908 SET hook = 'acqpo.activated'
3909 WHERE hook = 'format.po.jedi';
3910
3911 INSERT INTO action_trigger.reactor (module, description) VALUES (
3912     'AstCall', 'Possibly place a phone call with Asterisk'
3913 );
3914
3915 INSERT INTO
3916     action_trigger.event_definition (
3917         id, active, owner, name, hook, validator, reactor,
3918         cleanup_success, cleanup_failure, delay, delay_field, group_field,
3919         max_delay, granularity, usr_field, opt_in_setting, template
3920     ) VALUES (
3921         24,
3922         FALSE,
3923         1,
3924         'Telephone Overdue Notice',
3925         'checkout.due', 'NOOP_True', 'AstCall',
3926         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
3927         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
3928         $$
3929 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
3930 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
3931 Channel: [% channel_prefix %]/[% country %][% phone %]
3932 Context: overdue-test
3933 MaxRetries: 1
3934 RetryTime: 60
3935 WaitTime: 30
3936 Extension: 10
3937 Archive: 1
3938 Set: eg_user_id=[% target.0.usr.id %]
3939 Set: items=[% target.size %]
3940 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
3941 $$
3942     );
3943
3944 INSERT INTO
3945     action_trigger.environment (id, event_def, path)
3946     VALUES
3947         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
3948         (DEFAULT, 24, 'usr')
3949     ;
3950
3951 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3952         'circ.format.history.email',
3953         'circ', 
3954         oils_i18n_gettext(
3955             'circ.format.history.email',
3956             'An email has been requested for a circ history.',
3957             'ath',
3958             'description'
3959         ), 
3960         FALSE
3961     )
3962     ,(
3963         'circ.format.history.print',
3964         'circ', 
3965         oils_i18n_gettext(
3966             'circ.format.history.print',
3967             'A circ history needs to be formatted for printing.',
3968             'ath',
3969             'description'
3970         ), 
3971         FALSE
3972     )
3973     ,(
3974         'ahr.format.history.email',
3975         'ahr', 
3976         oils_i18n_gettext(
3977             'ahr.format.history.email',
3978             'An email has been requested for a hold request history.',
3979             'ath',
3980             'description'
3981         ), 
3982         FALSE
3983     )
3984     ,(
3985         'ahr.format.history.print',
3986         'ahr', 
3987         oils_i18n_gettext(
3988             'ahr.format.history.print',
3989             'A hold request history needs to be formatted for printing.',
3990             'ath',
3991             'description'
3992         ), 
3993         FALSE
3994     )
3995
3996 ;
3997
3998 INSERT INTO action_trigger.event_definition (
3999         id,
4000         active,
4001         owner,
4002         name,
4003         hook,
4004         validator,
4005         reactor,
4006         group_field,
4007         granularity,
4008         template
4009     ) VALUES (
4010         25,
4011         TRUE,
4012         1,
4013         'circ.history.email',
4014         'circ.format.history.email',
4015         'NOOP_True',
4016         'SendEmail',
4017         'usr',
4018         NULL,
4019 $$
4020 [%- USE date -%]
4021 [%- SET user = target.0.usr -%]
4022 To: [%- params.recipient_email || user.email %]
4023 From: [%- params.sender_email || default_sender %]
4024 Subject: Circulation History
4025
4026     [% FOR circ IN target %]
4027             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4028             Barcode: [% circ.target_copy.barcode %]
4029             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
4030             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
4031             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
4032     [% END %]
4033 $$
4034     )
4035     ,(
4036         26,
4037         TRUE,
4038         1,
4039         'circ.history.print',
4040         'circ.format.history.print',
4041         'NOOP_True',
4042         'ProcessTemplate',
4043         'usr',
4044         'print-on-demand',
4045 $$
4046 [%- USE date -%]
4047 <div>
4048     <style> li { padding: 8px; margin 5px; }</style>
4049     <div>[% date.format %]</div>
4050     <br/>
4051
4052     [% user.family_name %], [% user.first_given_name %]
4053     <ol>
4054     [% FOR circ IN target %]
4055         <li>
4056             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
4057             <div>Barcode: [% circ.target_copy.barcode %]</div>
4058             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
4059             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
4060             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
4061         </li>
4062     [% END %]
4063     </ol>
4064 </div>
4065 $$
4066     )
4067     ,(
4068         27,
4069         TRUE,
4070         1,
4071         'ahr.history.email',
4072         'ahr.format.history.email',
4073         'NOOP_True',
4074         'SendEmail',
4075         'usr',
4076         NULL,
4077 $$
4078 [%- USE date -%]
4079 [%- SET user = target.0.usr -%]
4080 To: [%- params.recipient_email || user.email %]
4081 From: [%- params.sender_email || default_sender %]
4082 Subject: Hold Request History
4083
4084     [% FOR hold IN target %]
4085             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
4086             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
4087             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
4088     [% END %]
4089 $$
4090     )
4091     ,(
4092         28,
4093         TRUE,
4094         1,
4095         'ahr.history.print',
4096         'ahr.format.history.print',
4097         'NOOP_True',
4098         'ProcessTemplate',
4099         'usr',
4100         'print-on-demand',
4101 $$
4102 [%- USE date -%]
4103 <div>
4104     <style> li { padding: 8px; margin 5px; }</style>
4105     <div>[% date.format %]</div>
4106     <br/>
4107
4108     [% user.family_name %], [% user.first_given_name %]
4109     <ol>
4110     [% FOR hold IN target %]
4111         <li>
4112             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
4113             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
4114             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
4115         </li>
4116     [% END %]
4117     </ol>
4118 </div>
4119 $$
4120     )
4121
4122 ;
4123
4124 INSERT INTO action_trigger.environment (
4125         event_def,
4126         path
4127     ) VALUES 
4128          ( 25, 'target_copy')
4129         ,( 25, 'usr' )
4130         ,( 26, 'target_copy' )
4131         ,( 26, 'usr' )
4132         ,( 27, 'current_copy' )
4133         ,( 27, 'usr' )
4134         ,( 28, 'current_copy' )
4135         ,( 28, 'usr' )
4136 ;
4137
4138 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4139         'money.format.payment_receipt.email',
4140         'mp', 
4141         oils_i18n_gettext(
4142             'money.format.payment_receipt.email',
4143             'An email has been requested for a payment receipt.',
4144             'ath',
4145             'description'
4146         ), 
4147         FALSE
4148     )
4149     ,(
4150         'money.format.payment_receipt.print',
4151         'mp', 
4152         oils_i18n_gettext(
4153             'money.format.payment_receipt.print',
4154             'A payment receipt needs to be formatted for printing.',
4155             'ath',
4156             'description'
4157         ), 
4158         FALSE
4159     )
4160 ;
4161
4162 INSERT INTO action_trigger.event_definition (
4163         id,
4164         active,
4165         owner,
4166         name,
4167         hook,
4168         validator,
4169         reactor,
4170         group_field,
4171         granularity,
4172         template
4173     ) VALUES (
4174         29,
4175         TRUE,
4176         1,
4177         'money.payment_receipt.email',
4178         'money.format.payment_receipt.email',
4179         'NOOP_True',
4180         'SendEmail',
4181         'xact.usr',
4182         NULL,
4183 $$
4184 [%- USE date -%]
4185 [%- SET user = target.0.xact.usr -%]
4186 To: [%- params.recipient_email || user.email %]
4187 From: [%- params.sender_email || default_sender %]
4188 Subject: Payment Receipt
4189
4190 [% date.format -%]
4191 [%- SET xact_mp_hash = {} -%]
4192 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
4193     [%- SET xact_id = mp.xact.id -%]
4194     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
4195     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
4196 [%- END -%]
4197 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
4198     [%- SET xact = xact_mp_hash.$xact_id.xact %]
4199 Transaction ID: [% xact_id %]
4200     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4201     [% ELSE %]Miscellaneous
4202     [% END %]
4203     Line item billings:
4204         [%- SET mb_type_hash = {} -%]
4205         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
4206             [%- IF mb.voided == 'f' -%]
4207                 [%- SET mb_type = mb.btype.id -%]
4208                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
4209                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
4210                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
4211                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
4212                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
4213             [%- END -%]
4214         [%- END -%]
4215         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
4216             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
4217                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4218                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4219             [%- ELSE -%][%# all other billings show individually %]
4220                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
4221                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4222                 [% END %]
4223             [% END %]
4224         [% END %]
4225     Line item payments:
4226         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4227             Payment ID: [% mp.id %]
4228                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4229                     [% CASE "cash_payment" %]cash
4230                     [% CASE "check_payment" %]check
4231                     [% CASE "credit_card_payment" %]credit card (
4232                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4233                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4234                         [% cc_chunks.last -%]
4235                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4236                     )
4237                     [% CASE "credit_payment" %]credit
4238                     [% CASE "forgive_payment" %]forgiveness
4239                     [% CASE "goods_payment" %]goods
4240                     [% CASE "work_payment" %]work
4241                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
4242         [% END %]
4243 [% END %]
4244 $$
4245     )
4246     ,(
4247         30,
4248         TRUE,
4249         1,
4250         'money.payment_receipt.print',
4251         'money.format.payment_receipt.print',
4252         'NOOP_True',
4253         'ProcessTemplate',
4254         'xact.usr',
4255         'print-on-demand',
4256 $$
4257 [%- USE date -%][%- SET user = target.0.xact.usr -%]
4258 <div style="li { padding: 8px; margin 5px; }">
4259     <div>[% date.format %]</div><br/>
4260     <ol>
4261     [% SET xact_mp_hash = {} %]
4262     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
4263         [% SET xact_id = mp.xact.id %]
4264         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
4265         [% xact_mp_hash.$xact_id.payments.push(mp) %]
4266     [% END %]
4267     [% FOR xact_id IN xact_mp_hash.keys.sort %]
4268         [% SET xact = xact_mp_hash.$xact_id.xact %]
4269         <li>Transaction ID: [% xact_id %]
4270             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4271             [% ELSE %]Miscellaneous
4272             [% END %]
4273             Line item billings:<ol>
4274                 [% SET mb_type_hash = {} %]
4275                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
4276                     [% IF mb.voided == 'f' %]
4277                         [% SET mb_type = mb.btype.id %]
4278                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
4279                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
4280                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
4281                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
4282                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
4283                     [% END %]
4284                 [% END %]
4285                 [% FOR mb_type IN mb_type_hash.keys.sort %]
4286                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
4287                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4288                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4289                     [% ELSE %][%# all other billings show individually %]
4290                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
4291                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4292                         [% END %]
4293                     [% END %]</li>
4294                 [% END %]
4295             </ol>
4296             Line item payments:<ol>
4297                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4298                     <li>Payment ID: [% mp.id %]
4299                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4300                             [% CASE "cash_payment" %]cash
4301                             [% CASE "check_payment" %]check
4302                             [% CASE "credit_card_payment" %]credit card (
4303                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4304                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4305                                 [% cc_chunks.last -%]
4306                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4307                             )
4308                             [% CASE "credit_payment" %]credit
4309                             [% CASE "forgive_payment" %]forgiveness
4310                             [% CASE "goods_payment" %]goods
4311                             [% CASE "work_payment" %]work
4312                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
4313                     </li>
4314                 [% END %]
4315             </ol>
4316         </li>
4317     [% END %]
4318     </ol>
4319 </div>
4320 $$
4321     )
4322 ;
4323
4324 INSERT INTO action_trigger.environment (
4325         event_def,
4326         path
4327     ) VALUES -- for fleshing mp objects
4328          ( 29, 'xact')
4329         ,( 29, 'xact.usr')
4330         ,( 29, 'xact.grocery' )
4331         ,( 29, 'xact.circulation' )
4332         ,( 29, 'xact.summary' )
4333         ,( 30, 'xact')
4334         ,( 30, 'xact.usr')
4335         ,( 30, 'xact.grocery' )
4336         ,( 30, 'xact.circulation' )
4337         ,( 30, 'xact.summary' )
4338 ;
4339
4340 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
4341     'DeleteTempBiblioBucket',
4342     oils_i18n_gettext(
4343         'DeleteTempBiblioBucket',
4344         'Deletes a cbreb object used as a target if it has a btype of "temp"',
4345         'atclean',
4346         'description'
4347     )
4348 );
4349
4350 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4351         'biblio.format.record_entry.email',
4352         'cbreb', 
4353         oils_i18n_gettext(
4354             'biblio.format.record_entry.email',
4355             'An email has been requested for one or more biblio record entries.',
4356             'ath',
4357             'description'
4358         ), 
4359         FALSE
4360     )
4361     ,(
4362         'biblio.format.record_entry.print',
4363         'cbreb', 
4364         oils_i18n_gettext(
4365             'biblio.format.record_entry.print',
4366             'One or more biblio record entries need to be formatted for printing.',
4367             'ath',
4368             'description'
4369         ), 
4370         FALSE
4371     )
4372 ;
4373
4374 INSERT INTO action_trigger.event_definition (
4375         id,
4376         active,
4377         owner,
4378         name,
4379         hook,
4380         validator,
4381         reactor,
4382         cleanup_success,
4383         cleanup_failure,
4384         group_field,
4385         granularity,
4386         template
4387     ) VALUES (
4388         31,
4389         TRUE,
4390         1,
4391         'biblio.record_entry.email',
4392         'biblio.format.record_entry.email',
4393         'NOOP_True',
4394         'SendEmail',
4395         'DeleteTempBiblioBucket',
4396         'DeleteTempBiblioBucket',
4397         'owner',
4398         NULL,
4399 $$
4400 [%- USE date -%]
4401 [%- SET user = target.0.owner -%]
4402 To: [%- params.recipient_email || user.email %]
4403 From: [%- params.sender_email || default_sender %]
4404 Subject: Bibliographic Records
4405
4406     [% FOR cbreb IN target %]
4407     [% FOR cbrebi IN cbreb.items %]
4408         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
4409         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
4410         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
4411         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4412
4413     [% END %]
4414     [% END %]
4415 $$
4416     )
4417     ,(
4418         32,
4419         TRUE,
4420         1,
4421         'biblio.record_entry.print',
4422         'biblio.format.record_entry.print',
4423         'NOOP_True',
4424         'ProcessTemplate',
4425         'DeleteTempBiblioBucket',
4426         'DeleteTempBiblioBucket',
4427         'owner',
4428         'print-on-demand',
4429 $$
4430 [%- USE date -%]
4431 <div>
4432     <style> li { padding: 8px; margin 5px; }</style>
4433     <ol>
4434     [% FOR cbreb IN target %]
4435     [% FOR cbrebi IN cbreb.items %]
4436         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
4437             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
4438             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
4439             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4440         </li>
4441     [% END %]
4442     [% END %]
4443     </ol>
4444 </div>
4445 $$
4446     )
4447 ;
4448
4449 INSERT INTO action_trigger.environment (
4450         event_def,
4451         path
4452     ) VALUES -- for fleshing cbreb objects
4453          ( 31, 'owner' )
4454         ,( 31, 'items' )
4455         ,( 31, 'items.target_biblio_record_entry' )
4456         ,( 31, 'items.target_biblio_record_entry.simple_record' )
4457         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
4458         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
4459         ,( 31, 'items.target_biblio_record_entry.notes' )
4460         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
4461         ,( 32, 'owner' )
4462         ,( 32, 'items' )
4463         ,( 32, 'items.target_biblio_record_entry' )
4464         ,( 32, 'items.target_biblio_record_entry.simple_record' )
4465         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
4466         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
4467         ,( 32, 'items.target_biblio_record_entry.notes' )
4468         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
4469 ;
4470
4471 INSERT INTO action_trigger.environment (
4472         event_def,
4473         path
4474     ) VALUES -- for fleshing mp objects
4475          ( 29, 'credit_card_payment')
4476         ,( 29, 'xact.billings')
4477         ,( 29, 'xact.billings.btype')
4478         ,( 30, 'credit_card_payment')
4479         ,( 30, 'xact.billings')
4480         ,( 30, 'xact.billings.btype')
4481 ;
4482
4483 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
4484     (   'circ.format.missing_pieces.slip.print',
4485         'circ', 
4486         oils_i18n_gettext(
4487             'circ.format.missing_pieces.slip.print',
4488             'A missing pieces slip needs to be formatted for printing.',
4489             'ath',
4490             'description'
4491         ), 
4492         FALSE
4493     )
4494     ,(  'circ.format.missing_pieces.letter.print',
4495         'circ', 
4496         oils_i18n_gettext(
4497             'circ.format.missing_pieces.letter.print',
4498             'A missing pieces patron letter needs to be formatted for printing.',
4499             'ath',
4500             'description'
4501         ), 
4502         FALSE
4503     )
4504 ;
4505
4506 INSERT INTO action_trigger.event_definition (
4507         id,
4508         active,
4509         owner,
4510         name,
4511         hook,
4512         validator,
4513         reactor,
4514         group_field,
4515         granularity,
4516         template
4517     ) VALUES (
4518         33,
4519         TRUE,
4520         1,
4521         'circ.missing_pieces.slip.print',
4522         'circ.format.missing_pieces.slip.print',
4523         'NOOP_True',
4524         'ProcessTemplate',
4525         'usr',
4526         'print-on-demand',
4527 $$
4528 [%- USE date -%]
4529 [%- SET user = target.0.usr -%]
4530 <div style="li { padding: 8px; margin 5px; }">
4531     <div>[% date.format %]</div><br/>
4532     Missing pieces for:
4533     <ol>
4534     [% FOR circ IN target %]
4535         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
4536             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4537         </li>
4538     [% END %]
4539     </ol>
4540 </div>
4541 $$
4542     )
4543     ,(
4544         34,
4545         TRUE,
4546         1,
4547         'circ.missing_pieces.letter.print',
4548         'circ.format.missing_pieces.letter.print',
4549         'NOOP_True',
4550         'ProcessTemplate',
4551         'usr',
4552         'print-on-demand',
4553 $$
4554 [%- USE date -%]
4555 [%- SET user = target.0.usr -%]
4556 [% date.format %]
4557 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
4558
4559 We are missing pieces for the following returned items:
4560 [% FOR circ IN target %]
4561 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
4562 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4563 [% END %]
4564
4565 Please return these pieces as soon as possible.
4566
4567 Thanks!
4568
4569 Library Staff
4570 $$
4571     )
4572 ;
4573
4574 INSERT INTO action_trigger.environment (
4575         event_def,
4576         path
4577     ) VALUES -- for fleshing circ objects
4578          ( 33, 'usr')
4579         ,( 33, 'target_copy')
4580         ,( 33, 'target_copy.circ_lib')
4581         ,( 33, 'target_copy.circ_lib.mailing_address')
4582         ,( 33, 'target_copy.circ_lib.billing_address')
4583         ,( 33, 'target_copy.call_number')
4584         ,( 33, 'target_copy.call_number.owning_lib')
4585         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
4586         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
4587         ,( 33, 'circ_lib')
4588         ,( 33, 'circ_lib.mailing_address')
4589         ,( 33, 'circ_lib.billing_address')
4590         ,( 34, 'usr')
4591         ,( 34, 'target_copy')
4592         ,( 34, 'target_copy.circ_lib')
4593         ,( 34, 'target_copy.circ_lib.mailing_address')
4594         ,( 34, 'target_copy.circ_lib.billing_address')
4595         ,( 34, 'target_copy.call_number')
4596         ,( 34, 'target_copy.call_number.owning_lib')
4597         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
4598         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
4599         ,( 34, 'circ_lib')
4600         ,( 34, 'circ_lib.mailing_address')
4601         ,( 34, 'circ_lib.billing_address')
4602 ;
4603
4604 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
4605     VALUES (   
4606         'ahr.format.pull_list',
4607         'ahr', 
4608         oils_i18n_gettext(
4609             'ahr.format.pull_list',
4610             'Format holds pull list for printing',
4611             'ath',
4612             'description'
4613         ), 
4614         FALSE
4615     );
4616
4617 INSERT INTO action_trigger.event_definition (
4618         id,
4619         active,
4620         owner,
4621         name,
4622         hook,
4623         validator,
4624         reactor,
4625         group_field,
4626         granularity,
4627         template
4628     ) VALUES (
4629         35,
4630         TRUE,
4631         1,
4632         'Holds Pull List',
4633         'ahr.format.pull_list',
4634         'NOOP_True',
4635         'ProcessTemplate',
4636         'pickup_lib',
4637         'print-on-demand',
4638 $$
4639 [%- USE date -%]
4640 <style>
4641     table { border-collapse: collapse; } 
4642     td { padding: 5px; border-bottom: 1px solid #888; } 
4643     th { font-weight: bold; }
4644 </style>
4645 [% 
4646     # Sort the holds into copy-location buckets
4647     # In the main print loop, sort each bucket by callnumber before printing
4648     SET holds_list = [];
4649     SET loc_data = [];
4650     SET current_location = target.0.current_copy.location.id;
4651     FOR hold IN target;
4652         IF current_location != hold.current_copy.location.id;
4653             SET current_location = hold.current_copy.location.id;
4654             holds_list.push(loc_data);
4655             SET loc_data = [];
4656         END;
4657         SET hold_data = {
4658             'hold' => hold,
4659             'callnumber' => hold.current_copy.call_number.label
4660         };
4661         loc_data.push(hold_data);
4662     END;
4663     holds_list.push(loc_data)
4664 %]
4665 <table>
4666     <thead>
4667         <tr>
4668             <th>Title</th>
4669             <th>Author</th>
4670             <th>Shelving Location</th>
4671             <th>Call Number</th>
4672             <th>Barcode</th>
4673             <th>Patron</th>
4674         </tr>
4675     </thead>
4676     <tbody>
4677     [% FOR loc_data IN holds_list  %]
4678         [% FOR hold_data IN loc_data.sort('callnumber') %]
4679             [% 
4680                 SET hold = hold_data.hold;
4681                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
4682             %]
4683             <tr>
4684                 <td>[% copy_data.title | truncate %]</td>
4685                 <td>[% copy_data.author | truncate %]</td>
4686                 <td>[% hold.current_copy.location.name %]</td>
4687                 <td>[% hold.current_copy.call_number.label %]</td>
4688                 <td>[% hold.current_copy.barcode %]</td>
4689                 <td>[% hold.usr.card.barcode %]</td>
4690             </tr>
4691         [% END %]
4692     [% END %]
4693     <tbody>
4694 </table>
4695 $$
4696 );
4697
4698 INSERT INTO action_trigger.environment (
4699         event_def,
4700         path
4701     ) VALUES
4702         (35, 'current_copy.location'),
4703         (35, 'current_copy.call_number'),
4704         (35, 'usr.card'),
4705         (35, 'pickup_lib')
4706 ;
4707
4708 INSERT INTO action_trigger.validator (module, description) VALUES ( 
4709     'HoldIsCancelled', 
4710     oils_i18n_gettext( 
4711         'HoldIsCancelled', 
4712         'Check whether a hold request is cancelled.', 
4713         'atval', 
4714         'description' 
4715     ) 
4716 );
4717
4718 -- Create the query schema, and the tables and views therein
4719
4720 DROP SCHEMA IF EXISTS sql CASCADE;
4721 DROP SCHEMA IF EXISTS query CASCADE;
4722
4723 CREATE SCHEMA query;
4724
4725 CREATE TABLE query.datatype (
4726         id              SERIAL            PRIMARY KEY,
4727         datatype_name   TEXT              NOT NULL UNIQUE,
4728         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
4729         is_composite    BOOL              NOT NULL DEFAULT FALSE,
4730         CONSTRAINT qdt_comp_not_num CHECK
4731         ( is_numeric IS FALSE OR is_composite IS FALSE )
4732 );
4733
4734 -- Define the most common datatypes in query.datatype.  Note that none of
4735 -- these stock datatypes specifies a width or precision.
4736
4737 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
4738 -- room for more stock datatypes if we ever want to add them.
4739
4740 SELECT setval( 'query.datatype_id_seq', 1000 );
4741
4742 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4743   VALUES (1, 'SMALLINT', true);
4744  
4745 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4746   VALUES (2, 'INTEGER', true);
4747  
4748 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4749   VALUES (3, 'BIGINT', true);
4750  
4751 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4752   VALUES (4, 'DECIMAL', true);
4753  
4754 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4755   VALUES (5, 'NUMERIC', true);
4756  
4757 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4758   VALUES (6, 'REAL', true);
4759  
4760 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4761   VALUES (7, 'DOUBLE PRECISION', true);
4762  
4763 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4764   VALUES (8, 'SERIAL', true);
4765  
4766 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4767   VALUES (9, 'BIGSERIAL', true);
4768  
4769 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4770   VALUES (10, 'MONEY', false);
4771  
4772 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4773   VALUES (11, 'VARCHAR', false);
4774  
4775 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4776   VALUES (12, 'CHAR', false);
4777  
4778 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4779   VALUES (13, 'TEXT', false);
4780  
4781 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4782   VALUES (14, '"char"', false);
4783  
4784 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4785   VALUES (15, 'NAME', false);
4786  
4787 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4788   VALUES (16, 'BYTEA', false);
4789  
4790 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4791   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
4792  
4793 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4794   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
4795  
4796 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4797   VALUES (19, 'DATE', false);
4798  
4799 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4800   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
4801  
4802 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4803   VALUES (21, 'TIME WITH TIME ZONE', false);
4804  
4805 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4806   VALUES (22, 'INTERVAL', false);
4807  
4808 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4809   VALUES (23, 'BOOLEAN', false);
4810  
4811 CREATE TABLE query.subfield (
4812         id              SERIAL            PRIMARY KEY,
4813         composite_type  INT               NOT NULL
4814                                           REFERENCES query.datatype(id)
4815                                           ON DELETE CASCADE
4816                                           DEFERRABLE INITIALLY DEFERRED,
4817         seq_no          INT               NOT NULL
4818                                           CONSTRAINT qsf_pos_seq_no
4819                                           CHECK( seq_no > 0 ),
4820         subfield_type   INT               NOT NULL
4821                                           REFERENCES query.datatype(id)
4822                                           DEFERRABLE INITIALLY DEFERRED,
4823         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
4824 );
4825
4826 CREATE TABLE query.function_sig (
4827         id              SERIAL            PRIMARY KEY,
4828         function_name   TEXT              NOT NULL,
4829         return_type     INT               REFERENCES query.datatype(id)
4830                                           DEFERRABLE INITIALLY DEFERRED,
4831         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
4832         CONSTRAINT qfd_rtn_or_aggr CHECK
4833         ( return_type IS NULL OR is_aggregate = FALSE )
4834 );
4835
4836 CREATE INDEX query_function_sig_name_idx 
4837         ON query.function_sig (function_name);
4838
4839 CREATE TABLE query.function_param_def (
4840         id              SERIAL            PRIMARY KEY,
4841         function_id     INT               NOT NULL
4842                                           REFERENCES query.function_sig( id )
4843                                           ON DELETE CASCADE
4844                                           DEFERRABLE INITIALLY DEFERRED,
4845         seq_no          INT               NOT NULL
4846                                           CONSTRAINT qfpd_pos_seq_no CHECK
4847                                           ( seq_no > 0 ),
4848         datatype        INT               NOT NULL
4849                                           REFERENCES query.datatype( id )
4850                                           DEFERRABLE INITIALLY DEFERRED,
4851         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
4852 );
4853
4854 CREATE TABLE  query.stored_query (
4855         id            SERIAL         PRIMARY KEY,
4856         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
4857                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
4858         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
4859         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
4860         from_clause   INT            , --REFERENCES query.from_clause
4861                                      --DEFERRABLE INITIALLY DEFERRED,
4862         where_clause  INT            , --REFERENCES query.expression
4863                                      --DEFERRABLE INITIALLY DEFERRED,
4864         having_clause INT            , --REFERENCES query.expression
4865                                      --DEFERRABLE INITIALLY DEFERRED
4866         limit_count   INT            , --REFERENCES query.expression( id )
4867                                      --DEFERRABLE INITIALLY DEFERRED,
4868         offset_count  INT            --REFERENCES query.expression( id )
4869                                      --DEFERRABLE INITIALLY DEFERRED
4870 );
4871
4872 -- (Foreign keys to be defined later after other tables are created)
4873
4874 CREATE TABLE query.query_sequence (
4875         id              SERIAL            PRIMARY KEY,
4876         parent_query    INT               NOT NULL
4877                                           REFERENCES query.stored_query
4878                                                                           ON DELETE CASCADE
4879                                                                           DEFERRABLE INITIALLY DEFERRED,
4880         seq_no          INT               NOT NULL,
4881         child_query     INT               NOT NULL
4882                                           REFERENCES query.stored_query
4883                                                                           ON DELETE CASCADE
4884                                                                           DEFERRABLE INITIALLY DEFERRED,
4885         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
4886 );
4887
4888 CREATE TABLE query.bind_variable (
4889         name          TEXT             PRIMARY KEY,
4890         type          TEXT             NOT NULL
4891                                            CONSTRAINT bind_variable_type CHECK
4892                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
4893         description   TEXT             NOT NULL,
4894         default_value TEXT,            -- to be encoded in JSON
4895         label         TEXT             NOT NULL
4896 );
4897
4898 CREATE TABLE query.expression (
4899         id            SERIAL        PRIMARY KEY,
4900         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
4901                                     ( type IN (
4902                                     'xbet',    -- between
4903                                     'xbind',   -- bind variable
4904                                     'xbool',   -- boolean
4905                                     'xcase',   -- case
4906                                     'xcast',   -- cast
4907                                     'xcol',    -- column
4908                                     'xex',     -- exists
4909                                     'xfunc',   -- function
4910                                     'xin',     -- in
4911                                     'xisnull', -- is null
4912                                     'xnull',   -- null
4913                                     'xnum',    -- number
4914                                     'xop',     -- operator
4915                                     'xser',    -- series
4916                                     'xstr',    -- string
4917                                     'xsubq'    -- subquery
4918                                                                 ) ),
4919         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
4920         parent_expr   INT           REFERENCES query.expression
4921                                     ON DELETE CASCADE
4922                                     DEFERRABLE INITIALLY DEFERRED,
4923         seq_no        INT           NOT NULL DEFAULT 1,
4924         literal       TEXT,
4925         table_alias   TEXT,
4926         column_name   TEXT,
4927         left_operand  INT           REFERENCES query.expression
4928                                     DEFERRABLE INITIALLY DEFERRED,
4929         operator      TEXT,
4930         right_operand INT           REFERENCES query.expression
4931                                     DEFERRABLE INITIALLY DEFERRED,
4932         function_id   INT           REFERENCES query.function_sig
4933                                     DEFERRABLE INITIALLY DEFERRED,
4934         subquery      INT           REFERENCES query.stored_query
4935                                     DEFERRABLE INITIALLY DEFERRED,
4936         cast_type     INT           REFERENCES query.datatype
4937                                     DEFERRABLE INITIALLY DEFERRED,
4938         negate        BOOL          NOT NULL DEFAULT FALSE,
4939         bind_variable TEXT          REFERENCES query.bind_variable
4940                                         DEFERRABLE INITIALLY DEFERRED
4941 );
4942
4943 CREATE UNIQUE INDEX query_expr_parent_seq
4944         ON query.expression( parent_expr, seq_no )
4945         WHERE parent_expr IS NOT NULL;
4946
4947 -- Due to some circular references, the following foreign key definitions
4948 -- had to be deferred until query.expression existed:
4949
4950 ALTER TABLE query.stored_query
4951         ADD FOREIGN KEY ( where_clause )
4952         REFERENCES query.expression( id )
4953         DEFERRABLE INITIALLY DEFERRED;
4954
4955 ALTER TABLE query.stored_query
4956         ADD FOREIGN KEY ( having_clause )
4957         REFERENCES query.expression( id )
4958         DEFERRABLE INITIALLY DEFERRED;
4959
4960 ALTER TABLE query.stored_query
4961     ADD FOREIGN KEY ( limit_count )
4962     REFERENCES query.expression( id )
4963     DEFERRABLE INITIALLY DEFERRED;
4964
4965 ALTER TABLE query.stored_query
4966     ADD FOREIGN KEY ( offset_count )
4967     REFERENCES query.expression( id )
4968     DEFERRABLE INITIALLY DEFERRED;
4969
4970 CREATE TABLE query.case_branch (
4971         id            SERIAL        PRIMARY KEY,
4972         parent_expr   INT           NOT NULL REFERENCES query.expression
4973                                     ON DELETE CASCADE
4974                                     DEFERRABLE INITIALLY DEFERRED,
4975         seq_no        INT           NOT NULL,
4976         condition     INT           REFERENCES query.expression
4977                                     DEFERRABLE INITIALLY DEFERRED,
4978         result        INT           NOT NULL REFERENCES query.expression
4979                                     DEFERRABLE INITIALLY DEFERRED,
4980         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
4981 );
4982
4983 CREATE TABLE query.from_relation (
4984         id               SERIAL        PRIMARY KEY,
4985         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
4986                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
4987         table_name       TEXT,
4988         class_name       TEXT,
4989         subquery         INT           REFERENCES query.stored_query,
4990         function_call    INT           REFERENCES query.expression,
4991         table_alias      TEXT,
4992         parent_relation  INT           REFERENCES query.from_relation
4993                                        ON DELETE CASCADE
4994                                        DEFERRABLE INITIALLY DEFERRED,
4995         seq_no           INT           NOT NULL DEFAULT 1,
4996         join_type        TEXT          CONSTRAINT good_join_type CHECK (
4997                                            join_type IS NULL OR join_type IN
4998                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
4999                                        ),
5000         on_clause        INT           REFERENCES query.expression
5001                                        DEFERRABLE INITIALLY DEFERRED,
5002         CONSTRAINT join_or_core CHECK (
5003         ( parent_relation IS NULL AND join_type IS NULL
5004           AND on_clause IS NULL )
5005         OR
5006         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
5007           AND on_clause IS NOT NULL )
5008         )
5009 );
5010
5011 CREATE UNIQUE INDEX from_parent_seq
5012         ON query.from_relation( parent_relation, seq_no )
5013         WHERE parent_relation IS NOT NULL;
5014
5015 -- The following foreign key had to be deferred until
5016 -- query.from_relation existed
5017
5018 ALTER TABLE query.stored_query
5019         ADD FOREIGN KEY (from_clause)
5020         REFERENCES query.from_relation
5021         DEFERRABLE INITIALLY DEFERRED;
5022
5023 CREATE TABLE query.record_column (
5024         id            SERIAL            PRIMARY KEY,
5025         from_relation INT               NOT NULL REFERENCES query.from_relation
5026                                         ON DELETE CASCADE
5027                                         DEFERRABLE INITIALLY DEFERRED,
5028         seq_no        INT               NOT NULL,
5029         column_name   TEXT              NOT NULL,
5030         column_type   INT               NOT NULL REFERENCES query.datatype
5031                                         ON DELETE CASCADE
5032                                                                         DEFERRABLE INITIALLY DEFERRED,
5033         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
5034 );
5035
5036 CREATE TABLE query.select_item (
5037         id               SERIAL         PRIMARY KEY,
5038         stored_query     INT            NOT NULL REFERENCES query.stored_query
5039                                         ON DELETE CASCADE
5040                                         DEFERRABLE INITIALLY DEFERRED,
5041         seq_no           INT            NOT NULL,
5042         expression       INT            NOT NULL REFERENCES query.expression
5043                                         DEFERRABLE INITIALLY DEFERRED,
5044         column_alias     TEXT,
5045         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
5046         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
5047 );
5048
5049 CREATE TABLE query.order_by_item (
5050         id               SERIAL         PRIMARY KEY,
5051         stored_query     INT            NOT NULL REFERENCES query.stored_query
5052                                         ON DELETE CASCADE
5053                                         DEFERRABLE INITIALLY DEFERRED,
5054         seq_no           INT            NOT NULL,
5055         expression       INT            NOT NULL REFERENCES query.expression
5056                                         ON DELETE CASCADE
5057                                         DEFERRABLE INITIALLY DEFERRED,
5058         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
5059 );
5060
5061 ------------------------------------------------------------
5062 -- Create updatable views for different kinds of expressions
5063 ------------------------------------------------------------
5064
5065 -- Create updatable view for BETWEEN expressions
5066
5067 CREATE OR REPLACE VIEW query.expr_xbet AS
5068     SELECT
5069                 id,
5070                 parenthesize,
5071                 parent_expr,
5072                 seq_no,
5073                 left_operand,
5074                 negate
5075     FROM
5076         query.expression
5077     WHERE
5078         type = 'xbet';
5079
5080 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
5081     ON INSERT TO query.expr_xbet
5082     DO INSTEAD
5083     INSERT INTO query.expression (
5084                 id,
5085                 type,
5086                 parenthesize,
5087                 parent_expr,
5088                 seq_no,
5089                 left_operand,
5090                 negate
5091     ) VALUES (
5092         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5093         'xbet',
5094         COALESCE(NEW.parenthesize, FALSE),
5095         NEW.parent_expr,
5096         COALESCE(NEW.seq_no, 1),
5097                 NEW.left_operand,
5098                 COALESCE(NEW.negate, false)
5099     );
5100
5101 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
5102     ON UPDATE TO query.expr_xbet
5103     DO INSTEAD
5104     UPDATE query.expression SET
5105         id = NEW.id,
5106         parenthesize = NEW.parenthesize,
5107         parent_expr = NEW.parent_expr,
5108         seq_no = NEW.seq_no,
5109                 left_operand = NEW.left_operand,
5110                 negate = NEW.negate
5111     WHERE
5112         id = OLD.id;
5113
5114 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
5115     ON DELETE TO query.expr_xbet
5116     DO INSTEAD
5117     DELETE FROM query.expression WHERE id = OLD.id;
5118
5119 -- Create updatable view for bind variable expressions
5120
5121 CREATE OR REPLACE VIEW query.expr_xbind AS
5122     SELECT
5123                 id,
5124                 parenthesize,
5125                 parent_expr,
5126                 seq_no,
5127                 bind_variable
5128     FROM
5129         query.expression
5130     WHERE
5131         type = 'xbind';
5132
5133 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
5134     ON INSERT TO query.expr_xbind
5135     DO INSTEAD
5136     INSERT INTO query.expression (
5137                 id,
5138                 type,
5139                 parenthesize,
5140                 parent_expr,
5141                 seq_no,
5142                 bind_variable
5143     ) VALUES (
5144         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5145         'xbind',
5146         COALESCE(NEW.parenthesize, FALSE),
5147         NEW.parent_expr,
5148         COALESCE(NEW.seq_no, 1),
5149                 NEW.bind_variable
5150     );
5151
5152 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
5153     ON UPDATE TO query.expr_xbind
5154     DO INSTEAD
5155     UPDATE query.expression SET
5156         id = NEW.id,
5157         parenthesize = NEW.parenthesize,
5158         parent_expr = NEW.parent_expr,
5159         seq_no = NEW.seq_no,
5160                 bind_variable = NEW.bind_variable
5161     WHERE
5162         id = OLD.id;
5163
5164 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
5165     ON DELETE TO query.expr_xbind
5166     DO INSTEAD
5167     DELETE FROM query.expression WHERE id = OLD.id;
5168
5169 -- Create updatable view for boolean expressions
5170
5171 CREATE OR REPLACE VIEW query.expr_xbool AS
5172     SELECT
5173                 id,
5174                 parenthesize,
5175                 parent_expr,
5176                 seq_no,
5177                 literal,
5178                 negate
5179     FROM
5180         query.expression
5181     WHERE
5182         type = 'xbool';
5183
5184 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
5185     ON INSERT TO query.expr_xbool
5186     DO INSTEAD
5187     INSERT INTO query.expression (
5188                 id,
5189                 type,
5190                 parenthesize,
5191                 parent_expr,
5192                 seq_no,
5193                 literal,
5194                 negate
5195     ) VALUES (
5196         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5197         'xbool',
5198         COALESCE(NEW.parenthesize, FALSE),
5199         NEW.parent_expr,
5200         COALESCE(NEW.seq_no, 1),
5201         NEW.literal,
5202                 COALESCE(NEW.negate, false)
5203     );
5204
5205 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
5206     ON UPDATE TO query.expr_xbool
5207     DO INSTEAD
5208     UPDATE query.expression SET
5209         id = NEW.id,
5210         parenthesize = NEW.parenthesize,
5211         parent_expr = NEW.parent_expr,
5212         seq_no = NEW.seq_no,
5213         literal = NEW.literal,
5214                 negate = NEW.negate
5215     WHERE
5216         id = OLD.id;
5217
5218 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
5219     ON DELETE TO query.expr_xbool
5220     DO INSTEAD
5221     DELETE FROM query.expression WHERE id = OLD.id;
5222
5223 -- Create updatable view for CASE expressions
5224
5225 CREATE OR REPLACE VIEW query.expr_xcase AS
5226     SELECT
5227                 id,
5228                 parenthesize,
5229                 parent_expr,
5230                 seq_no,
5231                 left_operand,
5232                 negate
5233     FROM
5234         query.expression
5235     WHERE
5236         type = 'xcase';
5237
5238 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
5239     ON INSERT TO query.expr_xcase
5240     DO INSTEAD
5241     INSERT INTO query.expression (
5242                 id,
5243                 type,
5244                 parenthesize,
5245                 parent_expr,
5246                 seq_no,
5247                 left_operand,
5248                 negate
5249     ) VALUES (
5250         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5251         'xcase',
5252         COALESCE(NEW.parenthesize, FALSE),
5253         NEW.parent_expr,
5254         COALESCE(NEW.seq_no, 1),
5255                 NEW.left_operand,
5256                 COALESCE(NEW.negate, false)
5257     );
5258
5259 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
5260     ON UPDATE TO query.expr_xcase
5261     DO INSTEAD
5262     UPDATE query.expression SET
5263         id = NEW.id,
5264         parenthesize = NEW.parenthesize,
5265         parent_expr = NEW.parent_expr,
5266         seq_no = NEW.seq_no,
5267                 left_operand = NEW.left_operand,
5268                 negate = NEW.negate
5269     WHERE
5270         id = OLD.id;
5271
5272 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
5273     ON DELETE TO query.expr_xcase
5274     DO INSTEAD
5275     DELETE FROM query.expression WHERE id = OLD.id;
5276
5277 -- Create updatable view for cast expressions
5278
5279 CREATE OR REPLACE VIEW query.expr_xcast AS
5280     SELECT
5281                 id,
5282                 parenthesize,
5283                 parent_expr,
5284                 seq_no,
5285                 left_operand,
5286                 cast_type,
5287                 negate
5288     FROM
5289         query.expression
5290     WHERE
5291         type = 'xcast';
5292
5293 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
5294     ON INSERT TO query.expr_xcast
5295     DO INSTEAD
5296     INSERT INTO query.expression (
5297         id,
5298         type,
5299         parenthesize,
5300         parent_expr,
5301         seq_no,
5302         left_operand,
5303         cast_type,
5304         negate
5305     ) VALUES (
5306         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5307         'xcast',
5308         COALESCE(NEW.parenthesize, FALSE),
5309         NEW.parent_expr,
5310         COALESCE(NEW.seq_no, 1),
5311         NEW.left_operand,
5312         NEW.cast_type,
5313         COALESCE(NEW.negate, false)
5314     );
5315
5316 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
5317     ON UPDATE TO query.expr_xcast
5318     DO INSTEAD
5319     UPDATE query.expression SET
5320         id = NEW.id,
5321         parenthesize = NEW.parenthesize,
5322         parent_expr = NEW.parent_expr,
5323         seq_no = NEW.seq_no,
5324                 left_operand = NEW.left_operand,
5325                 cast_type = NEW.cast_type,
5326                 negate = NEW.negate
5327     WHERE
5328         id = OLD.id;
5329
5330 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
5331     ON DELETE TO query.expr_xcast
5332     DO INSTEAD
5333     DELETE FROM query.expression WHERE id = OLD.id;
5334
5335 -- Create updatable view for column expressions
5336
5337 CREATE OR REPLACE VIEW query.expr_xcol AS
5338     SELECT
5339                 id,
5340                 parenthesize,
5341                 parent_expr,
5342                 seq_no,
5343                 table_alias,
5344                 column_name,
5345                 negate
5346     FROM
5347         query.expression
5348     WHERE
5349         type = 'xcol';
5350
5351 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
5352     ON INSERT TO query.expr_xcol
5353     DO INSTEAD
5354     INSERT INTO query.expression (
5355                 id,
5356                 type,
5357                 parenthesize,
5358                 parent_expr,
5359                 seq_no,
5360                 table_alias,
5361                 column_name,
5362                 negate
5363     ) VALUES (
5364         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5365         'xcol',
5366         COALESCE(NEW.parenthesize, FALSE),
5367         NEW.parent_expr,
5368         COALESCE(NEW.seq_no, 1),
5369                 NEW.table_alias,
5370                 NEW.column_name,
5371                 COALESCE(NEW.negate, false)
5372     );
5373
5374 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
5375     ON UPDATE TO query.expr_xcol
5376     DO INSTEAD
5377     UPDATE query.expression SET
5378         id = NEW.id,
5379         parenthesize = NEW.parenthesize,
5380         parent_expr = NEW.parent_expr,
5381         seq_no = NEW.seq_no,
5382                 table_alias = NEW.table_alias,
5383                 column_name = NEW.column_name,
5384                 negate = NEW.negate
5385     WHERE
5386         id = OLD.id;
5387
5388 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
5389     ON DELETE TO query.expr_xcol
5390     DO INSTEAD
5391     DELETE FROM query.expression WHERE id = OLD.id;
5392
5393 -- Create updatable view for EXISTS expressions
5394
5395 CREATE OR REPLACE VIEW query.expr_xex AS
5396     SELECT
5397                 id,
5398                 parenthesize,
5399                 parent_expr,
5400                 seq_no,
5401                 subquery,
5402                 negate
5403     FROM
5404         query.expression
5405     WHERE
5406         type = 'xex';
5407
5408 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
5409     ON INSERT TO query.expr_xex
5410     DO INSTEAD
5411     INSERT INTO query.expression (
5412                 id,
5413                 type,
5414                 parenthesize,
5415                 parent_expr,
5416                 seq_no,
5417                 subquery,
5418                 negate
5419     ) VALUES (
5420         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5421         'xex',
5422         COALESCE(NEW.parenthesize, FALSE),
5423         NEW.parent_expr,
5424         COALESCE(NEW.seq_no, 1),
5425                 NEW.subquery,
5426                 COALESCE(NEW.negate, false)
5427     );
5428
5429 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
5430     ON UPDATE TO query.expr_xex
5431     DO INSTEAD
5432     UPDATE query.expression SET
5433         id = NEW.id,
5434         parenthesize = NEW.parenthesize,
5435         parent_expr = NEW.parent_expr,
5436         seq_no = NEW.seq_no,
5437                 subquery = NEW.subquery,
5438                 negate = NEW.negate
5439     WHERE
5440         id = OLD.id;
5441
5442 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
5443     ON DELETE TO query.expr_xex
5444     DO INSTEAD
5445     DELETE FROM query.expression WHERE id = OLD.id;
5446
5447 -- Create updatable view for function call expressions
5448
5449 CREATE OR REPLACE VIEW query.expr_xfunc AS
5450     SELECT
5451         id,
5452         parenthesize,
5453         parent_expr,
5454         seq_no,
5455         column_name,
5456         function_id,
5457         negate
5458     FROM
5459         query.expression
5460     WHERE
5461         type = 'xfunc';
5462
5463 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
5464     ON INSERT TO query.expr_xfunc
5465     DO INSTEAD
5466     INSERT INTO query.expression (
5467         id,
5468         type,
5469         parenthesize,
5470         parent_expr,
5471         seq_no,
5472         column_name,
5473         function_id,
5474         negate
5475     ) VALUES (
5476         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5477         'xfunc',
5478         COALESCE(NEW.parenthesize, FALSE),
5479         NEW.parent_expr,
5480         COALESCE(NEW.seq_no, 1),
5481         NEW.column_name,
5482         NEW.function_id,
5483         COALESCE(NEW.negate, false)
5484     );
5485
5486 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
5487     ON UPDATE TO query.expr_xfunc
5488     DO INSTEAD
5489     UPDATE query.expression SET
5490         id = NEW.id,
5491         parenthesize = NEW.parenthesize,
5492         parent_expr = NEW.parent_expr,
5493         seq_no = NEW.seq_no,
5494         column_name = NEW.column_name,
5495         function_id = NEW.function_id,
5496         negate = NEW.negate
5497     WHERE
5498         id = OLD.id;
5499
5500 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
5501     ON DELETE TO query.expr_xfunc
5502     DO INSTEAD
5503     DELETE FROM query.expression WHERE id = OLD.id;
5504
5505 -- Create updatable view for IN expressions
5506
5507 CREATE OR REPLACE VIEW query.expr_xin AS
5508     SELECT
5509                 id,
5510                 parenthesize,
5511                 parent_expr,
5512                 seq_no,
5513                 left_operand,
5514                 subquery,
5515                 negate
5516     FROM
5517         query.expression
5518     WHERE
5519         type = 'xin';
5520
5521 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
5522     ON INSERT TO query.expr_xin
5523     DO INSTEAD
5524     INSERT INTO query.expression (
5525                 id,
5526                 type,
5527                 parenthesize,
5528                 parent_expr,
5529                 seq_no,
5530                 left_operand,
5531                 subquery,
5532                 negate
5533     ) VALUES (
5534         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5535         'xin',
5536         COALESCE(NEW.parenthesize, FALSE),
5537         NEW.parent_expr,
5538         COALESCE(NEW.seq_no, 1),
5539                 NEW.left_operand,
5540                 NEW.subquery,
5541                 COALESCE(NEW.negate, false)
5542     );
5543
5544 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
5545     ON UPDATE TO query.expr_xin
5546     DO INSTEAD
5547     UPDATE query.expression SET
5548         id = NEW.id,
5549         parenthesize = NEW.parenthesize,
5550         parent_expr = NEW.parent_expr,
5551         seq_no = NEW.seq_no,
5552                 left_operand = NEW.left_operand,
5553                 subquery = NEW.subquery,
5554                 negate = NEW.negate
5555     WHERE
5556         id = OLD.id;
5557
5558 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
5559     ON DELETE TO query.expr_xin
5560     DO INSTEAD
5561     DELETE FROM query.expression WHERE id = OLD.id;
5562
5563 -- Create updatable view for IS NULL expressions
5564
5565 CREATE OR REPLACE VIEW query.expr_xisnull AS
5566     SELECT
5567                 id,
5568                 parenthesize,
5569                 parent_expr,
5570                 seq_no,
5571                 left_operand,
5572                 negate
5573     FROM
5574         query.expression
5575     WHERE
5576         type = 'xisnull';
5577
5578 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
5579     ON INSERT TO query.expr_xisnull
5580     DO INSTEAD
5581     INSERT INTO query.expression (
5582                 id,
5583                 type,
5584                 parenthesize,
5585                 parent_expr,
5586                 seq_no,
5587                 left_operand,
5588                 negate
5589     ) VALUES (
5590         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5591         'xisnull',
5592         COALESCE(NEW.parenthesize, FALSE),
5593         NEW.parent_expr,
5594         COALESCE(NEW.seq_no, 1),
5595                 NEW.left_operand,
5596                 COALESCE(NEW.negate, false)
5597     );
5598
5599 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
5600     ON UPDATE TO query.expr_xisnull
5601     DO INSTEAD
5602     UPDATE query.expression SET
5603         id = NEW.id,
5604         parenthesize = NEW.parenthesize,
5605         parent_expr = NEW.parent_expr,
5606         seq_no = NEW.seq_no,
5607                 left_operand = NEW.left_operand,
5608                 negate = NEW.negate
5609     WHERE
5610         id = OLD.id;
5611
5612 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
5613     ON DELETE TO query.expr_xisnull
5614     DO INSTEAD
5615     DELETE FROM query.expression WHERE id = OLD.id;
5616
5617 -- Create updatable view for NULL expressions
5618
5619 CREATE OR REPLACE VIEW query.expr_xnull AS
5620     SELECT
5621                 id,
5622                 parenthesize,
5623                 parent_expr,
5624                 seq_no,
5625                 negate
5626     FROM
5627         query.expression
5628     WHERE
5629         type = 'xnull';
5630
5631 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
5632     ON INSERT TO query.expr_xnull
5633     DO INSTEAD
5634     INSERT INTO query.expression (
5635                 id,
5636                 type,
5637                 parenthesize,
5638                 parent_expr,
5639                 seq_no,
5640                 negate
5641     ) VALUES (
5642         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5643         'xnull',
5644         COALESCE(NEW.parenthesize, FALSE),
5645         NEW.parent_expr,
5646         COALESCE(NEW.seq_no, 1),
5647                 COALESCE(NEW.negate, false)
5648     );
5649
5650 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
5651     ON UPDATE TO query.expr_xnull
5652     DO INSTEAD
5653     UPDATE query.expression SET
5654         id = NEW.id,
5655         parenthesize = NEW.parenthesize,
5656         parent_expr = NEW.parent_expr,
5657         seq_no = NEW.seq_no,
5658                 negate = NEW.negate
5659     WHERE
5660         id = OLD.id;
5661
5662 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
5663     ON DELETE TO query.expr_xnull
5664     DO INSTEAD
5665     DELETE FROM query.expression WHERE id = OLD.id;
5666
5667 -- Create updatable view for numeric literal expressions
5668
5669 CREATE OR REPLACE VIEW query.expr_xnum AS
5670     SELECT
5671                 id,
5672                 parenthesize,
5673                 parent_expr,
5674                 seq_no,
5675                 literal
5676     FROM
5677         query.expression
5678     WHERE
5679         type = 'xnum';
5680
5681 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
5682     ON INSERT TO query.expr_xnum
5683     DO INSTEAD
5684     INSERT INTO query.expression (
5685                 id,
5686                 type,
5687                 parenthesize,
5688                 parent_expr,
5689                 seq_no,
5690                 literal
5691     ) VALUES (
5692         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5693         'xnum',
5694         COALESCE(NEW.parenthesize, FALSE),
5695         NEW.parent_expr,
5696         COALESCE(NEW.seq_no, 1),
5697         NEW.literal
5698     );
5699
5700 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
5701     ON UPDATE TO query.expr_xnum
5702     DO INSTEAD
5703     UPDATE query.expression SET
5704         id = NEW.id,
5705         parenthesize = NEW.parenthesize,
5706         parent_expr = NEW.parent_expr,
5707         seq_no = NEW.seq_no,
5708         literal = NEW.literal
5709     WHERE
5710         id = OLD.id;
5711
5712 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
5713     ON DELETE TO query.expr_xnum
5714     DO INSTEAD
5715     DELETE FROM query.expression WHERE id = OLD.id;
5716
5717 -- Create updatable view for operator expressions
5718
5719 CREATE OR REPLACE VIEW query.expr_xop AS
5720     SELECT
5721                 id,
5722                 parenthesize,
5723                 parent_expr,
5724                 seq_no,
5725                 left_operand,
5726                 operator,
5727                 right_operand,
5728                 negate
5729     FROM
5730         query.expression
5731     WHERE
5732         type = 'xop';
5733
5734 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
5735     ON INSERT TO query.expr_xop
5736     DO INSTEAD
5737     INSERT INTO query.expression (
5738                 id,
5739                 type,
5740                 parenthesize,
5741                 parent_expr,
5742                 seq_no,
5743                 left_operand,
5744                 operator,
5745                 right_operand,
5746                 negate
5747     ) VALUES (
5748         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5749         'xop',
5750         COALESCE(NEW.parenthesize, FALSE),
5751         NEW.parent_expr,
5752         COALESCE(NEW.seq_no, 1),
5753                 NEW.left_operand,
5754                 NEW.operator,
5755                 NEW.right_operand,
5756                 COALESCE(NEW.negate, false)
5757     );
5758
5759 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
5760     ON UPDATE TO query.expr_xop
5761     DO INSTEAD
5762     UPDATE query.expression SET
5763         id = NEW.id,
5764         parenthesize = NEW.parenthesize,
5765         parent_expr = NEW.parent_expr,
5766         seq_no = NEW.seq_no,
5767                 left_operand = NEW.left_operand,
5768                 operator = NEW.operator,
5769                 right_operand = NEW.right_operand,
5770                 negate = NEW.negate
5771     WHERE
5772         id = OLD.id;
5773
5774 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
5775     ON DELETE TO query.expr_xop
5776     DO INSTEAD
5777     DELETE FROM query.expression WHERE id = OLD.id;
5778
5779 -- Create updatable view for series expressions
5780 -- i.e. series of expressions separated by operators
5781
5782 CREATE OR REPLACE VIEW query.expr_xser AS
5783     SELECT
5784                 id,
5785                 parenthesize,
5786                 parent_expr,
5787                 seq_no,
5788                 operator,
5789                 negate
5790     FROM
5791         query.expression
5792     WHERE
5793         type = 'xser';
5794
5795 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
5796     ON INSERT TO query.expr_xser
5797     DO INSTEAD
5798     INSERT INTO query.expression (
5799                 id,
5800                 type,
5801                 parenthesize,
5802                 parent_expr,
5803                 seq_no,
5804                 operator,
5805                 negate
5806     ) VALUES (
5807         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5808         'xser',
5809         COALESCE(NEW.parenthesize, FALSE),
5810         NEW.parent_expr,
5811         COALESCE(NEW.seq_no, 1),
5812                 NEW.operator,
5813                 COALESCE(NEW.negate, false)
5814     );
5815
5816 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
5817     ON UPDATE TO query.expr_xser
5818     DO INSTEAD
5819     UPDATE query.expression SET
5820         id = NEW.id,
5821         parenthesize = NEW.parenthesize,
5822         parent_expr = NEW.parent_expr,
5823         seq_no = NEW.seq_no,
5824                 operator = NEW.operator,
5825                 negate = NEW.negate
5826     WHERE
5827         id = OLD.id;
5828
5829 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
5830     ON DELETE TO query.expr_xser
5831     DO INSTEAD
5832     DELETE FROM query.expression WHERE id = OLD.id;
5833
5834 -- Create updatable view for string literal expressions
5835
5836 CREATE OR REPLACE VIEW query.expr_xstr AS
5837     SELECT
5838         id,
5839         parenthesize,
5840         parent_expr,
5841         seq_no,
5842         literal
5843     FROM
5844         query.expression
5845     WHERE
5846         type = 'xstr';
5847
5848 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
5849     ON INSERT TO query.expr_xstr
5850     DO INSTEAD
5851     INSERT INTO query.expression (
5852         id,
5853         type,
5854         parenthesize,
5855         parent_expr,
5856         seq_no,
5857         literal
5858     ) VALUES (
5859         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5860         'xstr',
5861         COALESCE(NEW.parenthesize, FALSE),
5862         NEW.parent_expr,
5863         COALESCE(NEW.seq_no, 1),
5864         NEW.literal
5865     );
5866
5867 CREATE OR REPLACE RULE query_expr_string_update_rule AS
5868     ON UPDATE TO query.expr_xstr
5869     DO INSTEAD
5870     UPDATE query.expression SET
5871         id = NEW.id,
5872         parenthesize = NEW.parenthesize,
5873         parent_expr = NEW.parent_expr,
5874         seq_no = NEW.seq_no,
5875         literal = NEW.literal
5876     WHERE
5877         id = OLD.id;
5878
5879 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
5880     ON DELETE TO query.expr_xstr
5881     DO INSTEAD
5882     DELETE FROM query.expression WHERE id = OLD.id;
5883
5884 -- Create updatable view for subquery expressions
5885
5886 CREATE OR REPLACE VIEW query.expr_xsubq AS
5887     SELECT
5888                 id,
5889                 parenthesize,
5890                 parent_expr,
5891                 seq_no,
5892                 subquery,
5893                 negate
5894     FROM
5895         query.expression
5896     WHERE
5897         type = 'xsubq';
5898
5899 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
5900     ON INSERT TO query.expr_xsubq
5901     DO INSTEAD
5902     INSERT INTO query.expression (
5903                 id,
5904                 type,
5905                 parenthesize,
5906                 parent_expr,
5907                 seq_no,
5908                 subquery,
5909                 negate
5910     ) VALUES (
5911         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5912         'xsubq',
5913         COALESCE(NEW.parenthesize, FALSE),
5914         NEW.parent_expr,
5915         COALESCE(NEW.seq_no, 1),
5916                 NEW.subquery,
5917                 COALESCE(NEW.negate, false)
5918     );
5919
5920 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
5921     ON UPDATE TO query.expr_xsubq
5922     DO INSTEAD
5923     UPDATE query.expression SET
5924         id = NEW.id,
5925         parenthesize = NEW.parenthesize,
5926         parent_expr = NEW.parent_expr,
5927         seq_no = NEW.seq_no,
5928                 subquery = NEW.subquery,
5929                 negate = NEW.negate
5930     WHERE
5931         id = OLD.id;
5932
5933 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
5934     ON DELETE TO query.expr_xsubq
5935     DO INSTEAD
5936     DELETE FROM query.expression WHERE id = OLD.id;
5937
5938 CREATE TABLE action.fieldset (
5939     id              SERIAL          PRIMARY KEY,
5940     owner           INT             NOT NULL REFERENCES actor.usr (id)
5941                                     DEFERRABLE INITIALLY DEFERRED,
5942     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
5943                                     DEFERRABLE INITIALLY DEFERRED,
5944     status          TEXT            NOT NULL
5945                                     CONSTRAINT valid_status CHECK ( status in
5946                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
5947     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
5948     scheduled_time  TIMESTAMPTZ,
5949     applied_time    TIMESTAMPTZ,
5950     classname       TEXT            NOT NULL, -- an IDL class name
5951     name            TEXT            NOT NULL,
5952     stored_query    INT             REFERENCES query.stored_query (id)
5953                                     DEFERRABLE INITIALLY DEFERRED,
5954     pkey_value      TEXT,
5955     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
5956     CONSTRAINT fieldset_one_or_the_other CHECK (
5957         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
5958         (pkey_value IS NOT NULL AND stored_query IS NULL)
5959     )
5960     -- the CHECK constraint means we can update the fields for a single
5961     -- row without all the extra overhead involved in a query
5962 );
5963
5964 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
5965 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
5966
5967 CREATE TABLE action.fieldset_col_val (
5968     id              SERIAL  PRIMARY KEY,
5969     fieldset        INT     NOT NULL REFERENCES action.fieldset
5970                                          ON DELETE CASCADE
5971                                          DEFERRABLE INITIALLY DEFERRED,
5972     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
5973     val             TEXT,              -- value for the column ... NULL means, well, NULL
5974     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
5975 );
5976
5977 CREATE OR REPLACE FUNCTION action.apply_fieldset(
5978         fieldset_id IN INT,        -- id from action.fieldset
5979         table_name  IN TEXT,       -- table to be updated
5980         pkey_name   IN TEXT,       -- name of primary key column in that table
5981         query       IN TEXT        -- query constructed by qstore (for query-based
5982                                    --    fieldsets only; otherwise null
5983 )
5984 RETURNS TEXT AS $$
5985 DECLARE
5986         statement TEXT;
5987         fs_status TEXT;
5988         fs_pkey_value TEXT;
5989         fs_query TEXT;
5990         sep CHAR;
5991         status_code TEXT;
5992         msg TEXT;
5993         update_count INT;
5994         cv RECORD;
5995 BEGIN
5996         -- Sanity checks
5997         IF fieldset_id IS NULL THEN
5998                 RETURN 'Fieldset ID parameter is NULL';
5999         END IF;
6000         IF table_name IS NULL THEN
6001                 RETURN 'Table name parameter is NULL';
6002         END IF;
6003         IF pkey_name IS NULL THEN
6004                 RETURN 'Primary key name parameter is NULL';
6005         END IF;
6006         --
6007         statement := 'UPDATE ' || table_name || ' SET';
6008         --
6009         SELECT
6010                 status,
6011                 quote_literal( pkey_value )
6012         INTO
6013                 fs_status,
6014                 fs_pkey_value
6015         FROM
6016                 action.fieldset
6017         WHERE
6018                 id = fieldset_id;
6019         --
6020         IF fs_status IS NULL THEN
6021                 RETURN 'No fieldset found for id = ' || fieldset_id;
6022         ELSIF fs_status = 'APPLIED' THEN
6023                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
6024         END IF;
6025         --
6026         sep := '';
6027         FOR cv IN
6028                 SELECT  col,
6029                                 val
6030                 FROM    action.fieldset_col_val
6031                 WHERE   fieldset = fieldset_id
6032         LOOP
6033                 statement := statement || sep || ' ' || cv.col
6034                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
6035                 sep := ',';
6036         END LOOP;
6037         --
6038         IF sep = '' THEN
6039                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
6040         END IF;
6041         --
6042         -- Add the WHERE clause.  This differs according to whether it's a
6043         -- single-row fieldset or a query-based fieldset.
6044         --
6045         IF query IS NULL        AND fs_pkey_value IS NULL THEN
6046                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
6047         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
6048             fs_query := rtrim( query, ';' );
6049             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
6050                          || fs_query || ' );';
6051         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
6052                 statement := statement || ' WHERE ' || pkey_name || ' = '
6053                                      || fs_pkey_value || ';';
6054         ELSE  -- both are not null
6055                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
6056         END IF;
6057         --
6058         -- Execute the update
6059         --
6060         BEGIN
6061                 EXECUTE statement;
6062                 GET DIAGNOSTICS update_count = ROW_COUNT;
6063                 --
6064                 IF UPDATE_COUNT > 0 THEN
6065                         status_code := 'APPLIED';
6066                         msg := NULL;
6067                 ELSE
6068                         status_code := 'ERROR';
6069                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
6070         END IF;
6071         EXCEPTION WHEN OTHERS THEN
6072                 status_code := 'ERROR';
6073                 msg := 'Unable to apply fieldset ' || fieldset_id
6074                            || ': ' || sqlerrm;
6075         END;
6076         --
6077         -- Update fieldset status
6078         --
6079         UPDATE action.fieldset
6080         SET status       = status_code,
6081             applied_time = now()
6082         WHERE id = fieldset_id;
6083         --
6084         RETURN msg;
6085 END;
6086 $$ LANGUAGE plpgsql;
6087
6088 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
6089 /**
6090  * Applies a specified fieldset, using a supplied table name and primary
6091  * key name.  The query parameter should be non-null only for
6092  * query-based fieldsets.
6093  *
6094  * Returns NULL if successful, or an error message if not.
6095  */
6096 $$;
6097
6098 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
6099
6100 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
6101     SELECT  u.hold,
6102             c.circ_lib,
6103             count(*)
6104       FROM  action.unfulfilled_hold_list u
6105             JOIN asset.copy c ON (c.id = u.current_copy)
6106       GROUP BY 1,2;
6107
6108 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
6109     SELECT  hold,
6110             min(count)
6111       FROM  action.unfulfilled_hold_loops
6112       GROUP BY 1;
6113
6114 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
6115     SELECT  DISTINCT l.*
6116       FROM  action.unfulfilled_hold_loops l
6117             JOIN action.unfulfilled_hold_min_loop m USING (hold)
6118       WHERE l.count = m.min;
6119
6120 ALTER TABLE asset.copy
6121 ADD COLUMN dummy_isbn TEXT;
6122
6123 ALTER TABLE auditor.asset_copy_history
6124 ADD COLUMN dummy_isbn TEXT;
6125
6126 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
6127 -- Add corresponding new column to auditor.asset_copy_history
6128
6129 ALTER TABLE asset.copy
6130         ADD COLUMN status_changed_time TIMESTAMPTZ;
6131
6132 ALTER TABLE auditor.asset_copy_history
6133         ADD COLUMN status_changed_time TIMESTAMPTZ;
6134
6135 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
6136 RETURNS TRIGGER AS $$
6137 BEGIN
6138     IF NEW.status <> OLD.status THEN
6139         NEW.status_changed_time := now();
6140     END IF;
6141     RETURN NEW;
6142 END;
6143 $$ LANGUAGE plpgsql;
6144
6145 CREATE TRIGGER acp_status_changed_trig
6146         BEFORE UPDATE ON asset.copy
6147         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
6148
6149 ALTER TABLE asset.copy
6150 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6151
6152 ALTER TABLE auditor.asset_copy_history
6153 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6154
6155 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
6156 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
6157
6158 DROP INDEX IF EXISTS asset.copy_barcode_key;
6159 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
6160
6161 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
6162 -- AFTER INSERT OR UPDATE ON asset.copy
6163
6164 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
6165 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
6166
6167 -- Moke mostly parallel changes to action.circulation
6168 -- and action.aged_circulation
6169
6170 ALTER TABLE action.circulation
6171 ADD COLUMN workstation INT
6172     REFERENCES actor.workstation
6173         ON DELETE SET NULL
6174         DEFERRABLE INITIALLY DEFERRED;
6175
6176 ALTER TABLE action.aged_circulation
6177 ADD COLUMN workstation INT;
6178
6179 ALTER TABLE action.circulation
6180 ADD COLUMN parent_circ BIGINT
6181         REFERENCES action.circulation(id)
6182         DEFERRABLE INITIALLY DEFERRED;
6183
6184 CREATE UNIQUE INDEX circ_parent_idx
6185 ON action.circulation( parent_circ )
6186 WHERE parent_circ IS NOT NULL;
6187
6188 ALTER TABLE action.aged_circulation
6189 ADD COLUMN parent_circ BIGINT;
6190
6191 ALTER TABLE action.circulation
6192 ADD COLUMN checkin_workstation INT
6193         REFERENCES actor.workstation(id)
6194         ON DELETE SET NULL
6195         DEFERRABLE INITIALLY DEFERRED;
6196
6197 ALTER TABLE action.aged_circulation
6198 ADD COLUMN checkin_workstation INT;
6199
6200 ALTER TABLE action.circulation
6201 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6202
6203 ALTER TABLE action.aged_circulation
6204 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6205
6206 CREATE INDEX action_circulation_target_copy_idx
6207 ON action.circulation (target_copy);
6208
6209 CREATE INDEX action_aged_circulation_target_copy_idx
6210 ON action.aged_circulation (target_copy);
6211
6212 ALTER TABLE action.circulation
6213 DROP CONSTRAINT circulation_stop_fines_check;
6214
6215 ALTER TABLE action.circulation
6216         ADD CONSTRAINT circulation_stop_fines_check
6217         CHECK (stop_fines IN (
6218         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
6219
6220 -- Hard due-date functionality
6221 CREATE TABLE config.hard_due_date (
6222         id          SERIAL      PRIMARY KEY,
6223         name        TEXT        NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
6224         ceiling_date    TIMESTAMPTZ NOT NULL,
6225         forceto     BOOL        NOT NULL,
6226         owner       INT         NOT NULL
6227 );
6228
6229 CREATE TABLE config.hard_due_date_values (
6230     id                  SERIAL      PRIMARY KEY,
6231     hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
6232                                     DEFERRABLE INITIALLY DEFERRED,
6233     ceiling_date        TIMESTAMPTZ NOT NULL,
6234     active_date         TIMESTAMPTZ NOT NULL
6235 );
6236
6237 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN hard_due_date INT REFERENCES config.hard_due_date (id);
6238
6239 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
6240 DECLARE
6241     temp_value  config.hard_due_date_values%ROWTYPE;
6242     updated     INT := 0;
6243 BEGIN
6244     FOR temp_value IN
6245       SELECT  DISTINCT ON (hard_due_date) *
6246         FROM  config.hard_due_date_values
6247         WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
6248         ORDER BY active_date DESC -- Latest (nearest to us) active time
6249    LOOP
6250         UPDATE  config.hard_due_date
6251           SET   ceiling_date = temp_value.ceiling_date
6252           WHERE id = temp_value.hard_due_date
6253                 AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd
6254
6255         IF FOUND THEN
6256             updated := updated + 1;
6257         END IF;
6258     END LOOP;
6259
6260     RETURN updated;
6261 END;
6262 $func$ LANGUAGE plpgsql;
6263
6264 -- Correct some long-standing misspellings involving variations of "recur"
6265
6266 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
6267 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6268
6269 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
6270 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6271
6272 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
6273 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
6274
6275 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
6276
6277 -- Might as well keep the comment in sync as well
6278 COMMENT ON TABLE config.rule_recurring_fine IS $$
6279 /*
6280  * Copyright (C) 2005  Georgia Public Library Service 
6281  * Mike Rylander <mrylander@gmail.com>
6282  *
6283  * Circulation Recurring Fine rules
6284  *
6285  * Each circulation is given a recurring fine amount based on one of
6286  * these rules.  The recurrence_interval should not be any shorter
6287  * than the interval between runs of the fine_processor.pl script
6288  * (which is run from CRON), or you could miss fines.
6289  * 
6290  *
6291  * ****
6292  *
6293  * This program is free software; you can redistribute it and/or
6294  * modify it under the terms of the GNU General Public License
6295  * as published by the Free Software Foundation; either version 2
6296  * of the License, or (at your option) any later version.
6297  *
6298  * This program is distributed in the hope that it will be useful,
6299  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6300  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6301  * GNU General Public License for more details.
6302  */
6303 $$;
6304
6305 -- Extend the name change to some related views:
6306
6307 DROP VIEW IF EXISTS reporter.overdue_circs;
6308
6309 CREATE OR REPLACE VIEW reporter.overdue_circs AS
6310 SELECT  *
6311   FROM  action.circulation
6312     WHERE checkin_time is null
6313                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
6314                                 AND due_date < now();
6315
6316 DROP VIEW IF EXISTS stats.fleshed_circulation;
6317
6318 DROP VIEW IF EXISTS stats.fleshed_copy;
6319
6320 CREATE VIEW stats.fleshed_copy AS
6321         SELECT  cp.*,
6322         CAST(cp.create_date AS DATE) AS create_date_day,
6323         CAST(cp.edit_date AS DATE) AS edit_date_day,
6324         DATE_TRUNC('hour', cp.create_date) AS create_date_hour,
6325         DATE_TRUNC('hour', cp.edit_date) AS edit_date_hour,
6326                 cn.label AS call_number_label,
6327                 cn.owning_lib,
6328                 rd.item_lang,
6329                 rd.item_type,
6330                 rd.item_form
6331         FROM    asset.copy cp
6332                 JOIN asset.call_number cn ON (cp.call_number = cn.id)
6333                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
6334
6335 CREATE VIEW stats.fleshed_circulation AS
6336         SELECT  c.*,
6337                 CAST(c.xact_start AS DATE) AS start_date_day,
6338                 CAST(c.xact_finish AS DATE) AS finish_date_day,
6339                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
6340                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
6341                 cp.call_number_label,
6342                 cp.owning_lib,
6343                 cp.item_lang,
6344                 cp.item_type,
6345                 cp.item_form
6346         FROM    action.circulation c
6347                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
6348
6349 -- Drop a view temporarily in order to alter action.all_circulation, upon
6350 -- which it is dependent.  We will recreate the view later.
6351
6352 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
6353
6354 -- You would think that CREATE OR REPLACE would be enough, but in testing
6355 -- PostgreSQL complained about renaming the columns in the view. So we
6356 -- drop the view first.
6357 DROP VIEW IF EXISTS action.all_circulation;
6358
6359 CREATE OR REPLACE VIEW action.all_circulation AS
6360     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6361         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6362         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6363         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6364         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6365         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6366       FROM  action.aged_circulation
6367             UNION ALL
6368     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,
6369         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,
6370         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6371         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6372         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6373         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6374         circ.parent_circ
6375       FROM  action.circulation circ
6376         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6377         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6378         JOIN actor.usr p ON (circ.usr = p.id)
6379         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6380         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
6381
6382 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
6383
6384 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
6385  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
6386    FROM asset."copy" cp
6387    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
6388    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
6389    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
6390   GROUP BY cp.id;
6391
6392 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
6393
6394 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
6395
6396 -- Rebuild dependent views
6397
6398 DROP VIEW IF EXISTS action.billable_circulations;
6399
6400 CREATE OR REPLACE VIEW action.billable_circulations AS
6401     SELECT  *
6402       FROM  action.circulation
6403       WHERE xact_finish IS NULL;
6404
6405 DROP VIEW IF EXISTS action.open_circulation;
6406
6407 CREATE OR REPLACE VIEW action.open_circulation AS
6408     SELECT  *
6409       FROM  action.circulation
6410       WHERE checkin_time IS NULL
6411       ORDER BY due_date;
6412
6413 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
6414 DECLARE
6415 found char := 'N';
6416 BEGIN
6417
6418     -- If there are any renewals for this circulation, don't archive or delete
6419     -- it yet.   We'll do so later, when we archive and delete the renewals.
6420
6421     SELECT 'Y' INTO found
6422     FROM action.circulation
6423     WHERE parent_circ = OLD.id
6424     LIMIT 1;
6425
6426     IF found = 'Y' THEN
6427         RETURN NULL;  -- don't delete
6428         END IF;
6429
6430     -- Archive a copy of the old row to action.aged_circulation
6431
6432     INSERT INTO action.aged_circulation
6433         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6434         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6435         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6436         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6437         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6438         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
6439       SELECT
6440         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6441         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6442         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6443         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6444         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6445         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6446         FROM action.all_circulation WHERE id = OLD.id;
6447
6448     RETURN OLD;
6449 END;
6450 $$ LANGUAGE 'plpgsql';
6451
6452 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
6453
6454 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
6455
6456 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
6457
6458 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$
6459 DECLARE
6460     current_requestor_group    permission.grp_tree%ROWTYPE;
6461     requestor_object    actor.usr%ROWTYPE;
6462     user_object        actor.usr%ROWTYPE;
6463     item_object        asset.copy%ROWTYPE;
6464     item_cn_object        asset.call_number%ROWTYPE;
6465     rec_descriptor        metabib.rec_descriptor%ROWTYPE;
6466     current_mp_weight    FLOAT;
6467     matchpoint_weight    FLOAT;
6468     tmp_weight        FLOAT;
6469     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
6470     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
6471 BEGIN
6472     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6473     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
6474     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6475     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6476     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
6477
6478     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
6479
6480     IF NOT FOUND THEN
6481         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
6482     ELSE
6483         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
6484     END IF;
6485
6486     LOOP 
6487         -- for each potential matchpoint for this ou and group ...
6488         FOR current_mp IN
6489             SELECT    m.*
6490               FROM    config.hold_matrix_matchpoint m
6491               WHERE    m.requestor_grp = current_requestor_group.id AND m.active
6492               ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
6493                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
6494                     CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
6495                     CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
6496                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
6497                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
6498
6499             current_mp_weight := 5.0;
6500
6501             IF current_mp.circ_modifier IS NOT NULL THEN
6502                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6503             END IF;
6504
6505             IF current_mp.marc_type IS NOT NULL THEN
6506                 IF item_object.circ_as_type IS NOT NULL THEN
6507                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6508                 ELSE
6509                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6510                 END IF;
6511             END IF;
6512
6513             IF current_mp.marc_form IS NOT NULL THEN
6514                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6515             END IF;
6516
6517             IF current_mp.marc_vr_format IS NOT NULL THEN
6518                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6519             END IF;
6520
6521             IF current_mp.juvenile_flag IS NOT NULL THEN
6522                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6523             END IF;
6524
6525             IF current_mp.ref_flag IS NOT NULL THEN
6526                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6527             END IF;
6528
6529
6530             -- caclulate the rule match weight
6531             IF current_mp.item_owning_ou IS NOT NULL THEN
6532                 CONTINUE WHEN current_mp.item_owning_ou NOT IN (SELECT (actor.org_unit_ancestors(item_cn_object.owning_lib)).id);
6533                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
6534                 current_mp_weight := current_mp_weight - tmp_weight;
6535             END IF; 
6536
6537             IF current_mp.item_circ_ou IS NOT NULL THEN
6538                 CONTINUE WHEN current_mp.item_circ_ou NOT IN (SELECT (actor.org_unit_ancestors(item_object.circ_lib)).id);
6539                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
6540                 current_mp_weight := current_mp_weight - tmp_weight;
6541             END IF; 
6542
6543             IF current_mp.pickup_ou IS NOT NULL THEN
6544                 CONTINUE WHEN current_mp.pickup_ou NOT IN (SELECT (actor.org_unit_ancestors(pickup_ou)).id);
6545                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
6546                 current_mp_weight := current_mp_weight - tmp_weight;
6547             END IF; 
6548
6549             IF current_mp.request_ou IS NOT NULL THEN
6550                 CONTINUE WHEN current_mp.request_ou NOT IN (SELECT (actor.org_unit_ancestors(request_ou)).id);
6551                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
6552                 current_mp_weight := current_mp_weight - tmp_weight;
6553             END IF; 
6554
6555             IF current_mp.user_home_ou IS NOT NULL THEN
6556                 CONTINUE WHEN current_mp.user_home_ou NOT IN (SELECT (actor.org_unit_ancestors(user_object.home_ou)).id);
6557                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
6558                 current_mp_weight := current_mp_weight - tmp_weight;
6559             END IF; 
6560
6561             -- set the matchpoint if we found the best one
6562             IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
6563                 matchpoint = current_mp;
6564                 matchpoint_weight = current_mp_weight;
6565             END IF;
6566
6567         END LOOP;
6568
6569         EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6570
6571         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
6572     END LOOP;
6573
6574     RETURN matchpoint.id;
6575 END;
6576 $func$ LANGUAGE plpgsql;
6577
6578 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$
6579 DECLARE
6580     matchpoint_id        INT;
6581     user_object        actor.usr%ROWTYPE;
6582     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
6583     standing_penalty    config.standing_penalty%ROWTYPE;
6584     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
6585     transit_source        actor.org_unit%ROWTYPE;
6586     item_object        asset.copy%ROWTYPE;
6587     ou_skip              actor.org_unit_setting%ROWTYPE;
6588     result            action.matrix_test_result;
6589     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
6590     hold_count        INT;
6591     hold_transit_prox    INT;
6592     frozen_hold_count    INT;
6593     context_org_list    INT[];
6594     done            BOOL := FALSE;
6595 BEGIN
6596     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6597     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
6598
6599     result.success := TRUE;
6600
6601     -- Fail if we couldn't find a user
6602     IF user_object.id IS NULL THEN
6603         result.fail_part := 'no_user';
6604         result.success := FALSE;
6605         done := TRUE;
6606         RETURN NEXT result;
6607         RETURN;
6608     END IF;
6609
6610     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6611
6612     -- Fail if we couldn't find a copy
6613     IF item_object.id IS NULL THEN
6614         result.fail_part := 'no_item';
6615         result.success := FALSE;
6616         done := TRUE;
6617         RETURN NEXT result;
6618         RETURN;
6619     END IF;
6620
6621     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
6622     result.matchpoint := matchpoint_id;
6623
6624     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
6625
6626     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
6627     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
6628         result.fail_part := 'circ.holds.target_skip_me';
6629         result.success := FALSE;
6630         done := TRUE;
6631         RETURN NEXT result;
6632         RETURN;
6633     END IF;
6634
6635     -- Fail if user is barred
6636     IF user_object.barred IS TRUE THEN
6637         result.fail_part := 'actor.usr.barred';
6638         result.success := FALSE;
6639         done := TRUE;
6640         RETURN NEXT result;
6641         RETURN;
6642     END IF;
6643
6644     -- Fail if we couldn't find any matchpoint (requires a default)
6645     IF matchpoint_id IS NULL THEN
6646         result.fail_part := 'no_matchpoint';
6647         result.success := FALSE;
6648         done := TRUE;
6649         RETURN NEXT result;
6650         RETURN;
6651     END IF;
6652
6653     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
6654
6655     IF hold_test.holdable IS FALSE THEN
6656         result.fail_part := 'config.hold_matrix_test.holdable';
6657         result.success := FALSE;
6658         done := TRUE;
6659         RETURN NEXT result;
6660     END IF;
6661
6662     IF hold_test.transit_range IS NOT NULL THEN
6663         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
6664         IF hold_test.distance_is_from_owner THEN
6665             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;
6666         ELSE
6667             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
6668         END IF;
6669
6670         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
6671
6672         IF NOT FOUND THEN
6673             result.fail_part := 'transit_range';
6674             result.success := FALSE;
6675             done := TRUE;
6676             RETURN NEXT result;
6677         END IF;
6678     END IF;
6679  
6680     IF NOT retargetting THEN
6681         FOR standing_penalty IN
6682             SELECT  DISTINCT csp.*
6683               FROM  actor.usr_standing_penalty usp
6684                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6685               WHERE usr = match_user
6686                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6687                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6688                     AND csp.block_list LIKE '%HOLD%' LOOP
6689     
6690             result.fail_part := standing_penalty.name;
6691             result.success := FALSE;
6692             done := TRUE;
6693             RETURN NEXT result;
6694         END LOOP;
6695     
6696         IF hold_test.stop_blocked_user IS TRUE THEN
6697             FOR standing_penalty IN
6698                 SELECT  DISTINCT csp.*
6699                   FROM  actor.usr_standing_penalty usp
6700                         JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6701                   WHERE usr = match_user
6702                         AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6703                         AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6704                         AND csp.block_list LIKE '%CIRC%' LOOP
6705         
6706                 result.fail_part := standing_penalty.name;
6707                 result.success := FALSE;
6708                 done := TRUE;
6709                 RETURN NEXT result;
6710             END LOOP;
6711         END IF;
6712     
6713         IF hold_test.max_holds IS NOT NULL THEN
6714             SELECT    INTO hold_count COUNT(*)
6715               FROM    action.hold_request
6716               WHERE    usr = match_user
6717                 AND fulfillment_time IS NULL
6718                 AND cancel_time IS NULL
6719                 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
6720     
6721             IF hold_count >= hold_test.max_holds THEN
6722                 result.fail_part := 'config.hold_matrix_test.max_holds';
6723                 result.success := FALSE;
6724                 done := TRUE;
6725                 RETURN NEXT result;
6726             END IF;
6727         END IF;
6728     END IF;
6729
6730     IF item_object.age_protect IS NOT NULL THEN
6731         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
6732
6733         IF item_object.create_date + age_protect_object.age > NOW() THEN
6734             IF hold_test.distance_is_from_owner THEN
6735                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
6736             ELSE
6737                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
6738             END IF;
6739
6740             IF hold_transit_prox > age_protect_object.prox THEN
6741                 result.fail_part := 'config.rule_age_hold_protect.prox';
6742                 result.success := FALSE;
6743                 done := TRUE;
6744                 RETURN NEXT result;
6745             END IF;
6746         END IF;
6747     END IF;
6748
6749     IF NOT done THEN
6750         RETURN NEXT result;
6751     END IF;
6752
6753     RETURN;
6754 END;
6755 $func$ LANGUAGE plpgsql;
6756
6757 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$
6758     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE);
6759 $func$ LANGUAGE SQL;
6760
6761 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$
6762     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
6763 $func$ LANGUAGE SQL;
6764
6765 -- New post-delete trigger to propagate deletions to parent(s)
6766
6767 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
6768 BEGIN
6769
6770     -- Having deleted a renewal, we can delete the original circulation (or a previous
6771     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
6772     -- deletion of any prior parents, etc. recursively.
6773
6774     IF OLD.parent_circ IS NOT NULL THEN
6775         DELETE FROM action.circulation
6776         WHERE id = OLD.parent_circ;
6777     END IF;
6778
6779     RETURN OLD;
6780 END;
6781 $$ LANGUAGE 'plpgsql';
6782
6783 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
6784 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
6785
6786 -- This only gets inserted if there are no other id > 100 billing types
6787 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;
6788 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
6789
6790 -- Populate xact_type column in the materialized version of billable_xact_summary
6791
6792 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
6793 BEGIN
6794         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
6795                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
6796         RETURN NEW;
6797 END;
6798 $$ LANGUAGE PLPGSQL;
6799  
6800 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
6801 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
6802  
6803 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
6804 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
6805
6806 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
6807     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;
6808
6809 -- Generate the equivalent of compound subject entries from the existing rows
6810 -- so that we don't have to laboriously reindex them
6811
6812 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
6813 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
6814 --
6815 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
6816 --
6817 --INSERT INTO metabib.subject_field_entry (source, field, value)
6818 --    SELECT source, (
6819 --            SELECT id 
6820 --            FROM config.metabib_field
6821 --            WHERE field_class = 'subject' AND name = 'complete'
6822 --        ), 
6823 --        ARRAY_TO_STRING ( 
6824 --            ARRAY (
6825 --                SELECT value 
6826 --                FROM metabib.subject_field_entry msfe
6827 --                WHERE msfe.source = groupee.source
6828 --                ORDER BY source 
6829 --            ), ' ' 
6830 --        ) AS grouped
6831 --    FROM ( 
6832 --        SELECT source
6833 --        FROM metabib.subject_field_entry
6834 --        GROUP BY source
6835 --    ) AS groupee;
6836
6837 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
6838 DECLARE
6839         prev_billing    money.billing%ROWTYPE;
6840         old_billing     money.billing%ROWTYPE;
6841 BEGIN
6842         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
6843         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
6844
6845         IF OLD.id = old_billing.id THEN
6846                 UPDATE  money.materialized_billable_xact_summary
6847                   SET   last_billing_ts = prev_billing.billing_ts,
6848                         last_billing_note = prev_billing.note,
6849                         last_billing_type = prev_billing.billing_type
6850                   WHERE id = OLD.xact;
6851         END IF;
6852
6853         IF NOT OLD.voided THEN
6854                 UPDATE  money.materialized_billable_xact_summary
6855                   SET   total_owed = total_owed - OLD.amount,
6856                         balance_owed = balance_owed + OLD.amount
6857                   WHERE id = OLD.xact;
6858         END IF;
6859
6860         RETURN OLD;
6861 END;
6862 $$ LANGUAGE PLPGSQL;
6863
6864 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
6865 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
6866
6867 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
6868     use Unicode::Normalize;
6869     use Encode;
6870
6871     # When working with Unicode data, the first step is to decode it to
6872     # a byte string; after that, lowercasing is safe
6873     my $txt = lc(decode_utf8(shift));
6874     my $sf = shift;
6875
6876     $txt = NFD($txt);
6877     $txt =~ s/\pM+//go; # Remove diacritics
6878
6879     # remove non-combining diacritics
6880     # this list of characters follows the NACO normalization spec,
6881     # but a looser but more comprehensive version might be
6882     # $txt =~ s/\pLm+//go;
6883     $txt =~ tr/\x{02B9}\x{02BA}\x{02BB}\x{02BC}//d;
6884
6885     $txt =~ s/\xE6/AE/go;   # Convert ae digraph
6886     $txt =~ s/\x{153}/OE/go;# Convert oe digraph
6887     $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
6888
6889     $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
6890     $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
6891
6892     $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;     # Convert Latin and Greek
6893     $txt =~ tr/\x{2113}\xF0\x{111}\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LDD /; # Convert Misc
6894     $txt =~ tr/\'\[\]\|//d;                         # Remove Misc
6895
6896     if ($sf && $sf =~ /^a/o) {
6897         my $commapos = index($txt,',');
6898         if ($commapos > -1) {
6899             if ($commapos != length($txt) - 1) {
6900                 my @list = split /,/, $txt;
6901                 my $first = shift @list;
6902                 $txt = $first . ',' . join(' ', @list);
6903             } else {
6904                 $txt =~ s/,/ /go;
6905             }
6906         }
6907     } else {
6908         $txt =~ s/,/ /go;
6909     }
6910
6911     $txt =~ s/\s+/ /go; # Compress multiple spaces
6912     $txt =~ s/^\s+//o;  # Remove leading space
6913     $txt =~ s/\s+$//o;  # Remove trailing space
6914
6915     # Encoding the outgoing string is good practice, but not strictly
6916     # necessary in this case because we've stripped everything from it
6917     return encode_utf8($txt);
6918 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
6919
6920 -- Some handy functions, based on existing ones, to provide optional ingest normalization
6921
6922 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6923         SELECT SUBSTRING($1,$2);
6924 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6925
6926 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
6927         SELECT SUBSTRING($1,1,$2);
6928 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6929
6930 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
6931         SELECT public.naco_normalize($1,'a');
6932 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6933
6934 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
6935         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
6936 $func$ LANGUAGE SQL STRICT IMMUTABLE;
6937
6938 -- And ... a table in which to register them
6939
6940 CREATE TABLE config.index_normalizer (
6941         id              SERIAL  PRIMARY KEY,
6942         name            TEXT    UNIQUE NOT NULL,
6943         description     TEXT,
6944         func            TEXT    NOT NULL,
6945         param_count     INT     NOT NULL DEFAULT 0
6946 );
6947
6948 CREATE TABLE config.metabib_field_index_norm_map (
6949         id      SERIAL  PRIMARY KEY,
6950         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6951         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6952         params  TEXT,
6953         pos     INT     NOT NULL DEFAULT 0
6954 );
6955
6956 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6957         'NACO Normalize',
6958         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
6959         'naco_normalize',
6960         0
6961 );
6962
6963 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6964         'Normalize date range',
6965         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
6966         'split_date_range',
6967         1
6968 );
6969
6970 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6971         'NACO Normalize -- retain first comma',
6972         '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.',
6973         'naco_normalize_keep_comma',
6974         0
6975 );
6976
6977 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6978         'Strip Diacritics',
6979         'Convert text to NFD form and remove non-spacing combining marks.',
6980         'remove_diacritics',
6981         0
6982 );
6983
6984 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6985         'Up-case',
6986         'Convert text upper case.',
6987         'uppercase',
6988         0
6989 );
6990
6991 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6992         'Down-case',
6993         'Convert text lower case.',
6994         'lowercase',
6995         0
6996 );
6997
6998 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6999         'Extract Dewey-like number',
7000         'Extract a string of numeric characters ther resembles a DDC number.',
7001         'call_number_dewey',
7002         0
7003 );
7004
7005 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7006         'Left truncation',
7007         'Discard the specified number of characters from the left side of the string.',
7008         'left_trunc',
7009         1
7010 );
7011
7012 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7013         'Right truncation',
7014         'Include only the specified number of characters from the left side of the string.',
7015         'right_trunc',
7016         1
7017 );
7018
7019 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7020         'First word',
7021         'Include only the first space-separated word of a string.',
7022         'first_word',
7023         0
7024 );
7025
7026 INSERT INTO config.metabib_field_index_norm_map (field,norm)
7027         SELECT  m.id,
7028                 i.id
7029           FROM  config.metabib_field m,
7030                 config.index_normalizer i
7031           WHERE i.func IN ('naco_normalize','split_date_range');
7032
7033 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
7034 DECLARE
7035     normalizer      RECORD;
7036     value           TEXT := '';
7037 BEGIN
7038
7039     value := NEW.value;
7040
7041     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7042         FOR normalizer IN
7043             SELECT  n.func AS func,
7044                     n.param_count AS param_count,
7045                     m.params AS params
7046               FROM  config.index_normalizer n
7047                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7048               WHERE field = NEW.field AND m.pos < 0
7049               ORDER BY m.pos LOOP
7050                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7051                     quote_literal( value ) ||
7052                     CASE
7053                         WHEN normalizer.param_count > 0
7054                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7055                             ELSE ''
7056                         END ||
7057                     ')' INTO value;
7058
7059         END LOOP;
7060
7061         NEW.value := value;
7062     END IF;
7063
7064     IF NEW.index_vector = ''::tsvector THEN
7065         RETURN NEW;
7066     END IF;
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     END IF;
7088
7089     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
7090         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
7091     ELSE
7092         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
7093     END IF;
7094
7095     RETURN NEW;
7096 END;
7097 $$ LANGUAGE PLPGSQL;
7098
7099 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7100
7101 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7102
7103 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7104     SELECT  ARRAY_TO_STRING(
7105                 oils_xpath(
7106                     $1 ||
7107                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
7108                     $2,
7109                     $4
7110                 ),
7111                 $3
7112             );
7113 $func$ LANGUAGE SQL IMMUTABLE;
7114
7115 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
7116     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
7117 $func$ LANGUAGE SQL IMMUTABLE;
7118
7119 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7120     SELECT oils_xpath_string( $1, $2, '', $3 );
7121 $func$ LANGUAGE SQL IMMUTABLE;
7122
7123 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
7124     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
7125 $func$ LANGUAGE SQL IMMUTABLE;
7126
7127 CREATE TYPE metabib.field_entry_template AS (
7128         field_class     TEXT,
7129         field           INT,
7130         source          BIGINT,
7131         value           TEXT
7132 );
7133
7134 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7135   use strict;
7136
7137   use XML::LibXSLT;
7138   use XML::LibXML;
7139
7140   my $doc = shift;
7141   my $xslt = shift;
7142
7143   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7144   # methods of parsing XML documents and stylesheets, in the hopes of broader
7145   # compatibility with distributions
7146   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7147
7148   # Cache the XML parser, if we do not already have one
7149   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7150     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7151
7152   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7153
7154   # Cache the XSLT processor, if we do not already have one
7155   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7156     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7157
7158   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7159     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7160
7161   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7162     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7163
7164   return $stylesheet->output_string(
7165     $stylesheet->transform(
7166       $parser->parse_string($doc)
7167     )
7168   );
7169
7170 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7171
7172 -- Add two columns so that the following function will compile.
7173 -- Eventually the label column will be NOT NULL, but not yet.
7174 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7175 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7176
7177 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7178 DECLARE
7179     bib     biblio.record_entry%ROWTYPE;
7180     idx     config.metabib_field%ROWTYPE;
7181     xfrm        config.xml_transform%ROWTYPE;
7182     prev_xfrm   TEXT;
7183     transformed_xml TEXT;
7184     xml_node    TEXT;
7185     xml_node_list   TEXT[];
7186     facet_text  TEXT;
7187     raw_text    TEXT;
7188     curr_text   TEXT;
7189     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7190     output_row  metabib.field_entry_template%ROWTYPE;
7191 BEGIN
7192
7193     -- Get the record
7194     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7195
7196     -- Loop over the indexing entries
7197     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7198
7199         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7200
7201         -- See if we can skip the XSLT ... it's expensive
7202         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7203             -- Can't skip the transform
7204             IF xfrm.xslt <> '---' THEN
7205                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7206             ELSE
7207                 transformed_xml := bib.marc;
7208             END IF;
7209
7210             prev_xfrm := xfrm.name;
7211         END IF;
7212
7213         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7214
7215         raw_text := NULL;
7216         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7217             CONTINUE WHEN xml_node !~ E'^\\s*<';
7218
7219             curr_text := ARRAY_TO_STRING(
7220                 oils_xpath( '//text()',
7221                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7222                         REGEXP_REPLACE( -- This escapes embeded <s
7223                             xml_node,
7224                             $re$(>[^<]+)(<)([^>]+<)$re$,
7225                             E'\\1&lt;\\3',
7226                             'g'
7227                         ),
7228                         '&(?!amp;)',
7229                         '&amp;',
7230                         'g'
7231                     )
7232                 ),
7233                 ' '
7234             );
7235
7236             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7237
7238             IF raw_text IS NOT NULL THEN
7239                 raw_text := raw_text || joiner;
7240             END IF;
7241
7242             raw_text := COALESCE(raw_text,'') || curr_text;
7243
7244             -- insert raw node text for faceting
7245             IF idx.facet_field THEN
7246
7247                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7248                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7249                 ELSE
7250                     facet_text := curr_text;
7251                 END IF;
7252
7253                 output_row.field_class = idx.field_class;
7254                 output_row.field = -1 * idx.id;
7255                 output_row.source = rid;
7256                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7257
7258                 RETURN NEXT output_row;
7259             END IF;
7260
7261         END LOOP;
7262
7263         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7264
7265         -- insert combined node text for searching
7266         IF idx.search_field THEN
7267             output_row.field_class = idx.field_class;
7268             output_row.field = idx.id;
7269             output_row.source = rid;
7270             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7271
7272             RETURN NEXT output_row;
7273         END IF;
7274
7275     END LOOP;
7276
7277 END;
7278 $func$ LANGUAGE PLPGSQL;
7279
7280 -- default to a space joiner
7281 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7282         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7283 $func$ LANGUAGE SQL;
7284
7285 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7286
7287 use MARC::Record;
7288 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7289
7290 my $xml = shift;
7291 my $r = MARC::Record->new_from_xml( $xml );
7292
7293 return_next( { tag => 'LDR', value => $r->leader } );
7294
7295 for my $f ( $r->fields ) {
7296     if ($f->is_control_field) {
7297         return_next({ tag => $f->tag, value => $f->data });
7298     } else {
7299         for my $s ($f->subfields) {
7300             return_next({
7301                 tag      => $f->tag,
7302                 ind1     => $f->indicator(1),
7303                 ind2     => $f->indicator(2),
7304                 subfield => $s->[0],
7305                 value    => $s->[1]
7306             });
7307
7308             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7309                 my $trim = $f->indicator(2) || 0;
7310                 return_next({
7311                     tag      => 'tnf',
7312                     ind1     => $f->indicator(1),
7313                     ind2     => $f->indicator(2),
7314                     subfield => 'a',
7315                     value    => substr( $s->[1], $trim )
7316                 });
7317             }
7318         }
7319     }
7320 }
7321
7322 return undef;
7323
7324 $func$ LANGUAGE PLPERLU;
7325
7326 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7327 DECLARE
7328     bib biblio.record_entry%ROWTYPE;
7329     output  metabib.full_rec%ROWTYPE;
7330     field   RECORD;
7331 BEGIN
7332     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7333
7334     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7335         output.record := rid;
7336         output.ind1 := field.ind1;
7337         output.ind2 := field.ind2;
7338         output.tag := field.tag;
7339         output.subfield := field.subfield;
7340         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7341             output.value := naco_normalize(field.value, field.subfield);
7342         ELSE
7343             output.value := field.value;
7344         END IF;
7345
7346         CONTINUE WHEN output.value IS NULL;
7347
7348         RETURN NEXT output;
7349     END LOOP;
7350 END;
7351 $func$ LANGUAGE PLPGSQL;
7352
7353 -- functions to create auditor objects
7354
7355 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7356 BEGIN
7357     EXECUTE $$
7358         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7359     $$;
7360         RETURN TRUE;
7361 END;
7362 $creator$ LANGUAGE 'plpgsql';
7363
7364 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7365 BEGIN
7366     EXECUTE $$
7367         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7368             audit_id    BIGINT                          PRIMARY KEY,
7369             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7370             audit_action        TEXT                            NOT NULL,
7371             LIKE $$ || sch || $$.$$ || tbl || $$
7372         );
7373     $$;
7374         RETURN TRUE;
7375 END;
7376 $creator$ LANGUAGE 'plpgsql';
7377
7378 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7379 BEGIN
7380     EXECUTE $$
7381         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7382         RETURNS TRIGGER AS $func$
7383         BEGIN
7384             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7385                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7386                     now(),
7387                     SUBSTR(TG_OP,1,1),
7388                     OLD.*;
7389             RETURN NULL;
7390         END;
7391         $func$ LANGUAGE 'plpgsql';
7392     $$;
7393         RETURN TRUE;
7394 END;
7395 $creator$ LANGUAGE 'plpgsql';
7396
7397 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7398 BEGIN
7399     EXECUTE $$
7400         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7401             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7402             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7403     $$;
7404         RETURN TRUE;
7405 END;
7406 $creator$ LANGUAGE 'plpgsql';
7407
7408 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7409 BEGIN
7410     EXECUTE $$
7411         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7412             SELECT      -1, now() as audit_time, '-' as audit_action, *
7413               FROM      $$ || sch || $$.$$ || tbl || $$
7414                 UNION ALL
7415             SELECT      *
7416               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7417     $$;
7418         RETURN TRUE;
7419 END;
7420 $creator$ LANGUAGE 'plpgsql';
7421
7422 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7423
7424 -- The main event
7425
7426 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7427 BEGIN
7428     PERFORM auditor.create_auditor_seq(sch, tbl);
7429     PERFORM auditor.create_auditor_history(sch, tbl);
7430     PERFORM auditor.create_auditor_func(sch, tbl);
7431     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7432     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7433     RETURN TRUE;
7434 END;
7435 $creator$ LANGUAGE 'plpgsql';
7436
7437 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7438
7439 ALTER TABLE action.hold_request
7440 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7441
7442 ALTER TABLE action.hold_request
7443 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7444
7445 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7446
7447 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7448
7449 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7450
7451 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7452
7453 -- Add claims_never_checked_out_count to actor.usr, related history
7454
7455 ALTER TABLE actor.usr ADD COLUMN
7456         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7457
7458 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7459         claims_never_checked_out_count INT;
7460
7461 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7462
7463 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7464
7465 -----------
7466
7467 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7468 BEGIN
7469         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7470                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7471                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7472                 END IF;
7473                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7474                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7475                 END IF;
7476                 IF NEW.stop_fines = 'LOST' THEN
7477                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7478                 END IF;
7479         END IF;
7480         RETURN NEW;
7481 END;
7482 $$ LANGUAGE 'plpgsql';
7483
7484 -- Create new table acq.fund_allocation_percent
7485 -- Populate it from acq.fund_allocation
7486 -- Convert all percentages to amounts in acq.fund_allocation
7487
7488 CREATE TABLE acq.fund_allocation_percent
7489 (
7490     id                   SERIAL            PRIMARY KEY,
7491     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7492                                                DEFERRABLE INITIALLY DEFERRED,
7493     org                  INT               NOT NULL REFERENCES actor.org_unit
7494                                                DEFERRABLE INITIALLY DEFERRED,
7495     fund_code            TEXT,
7496     percent              NUMERIC           NOT NULL,
7497     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7498                                                DEFERRABLE INITIALLY DEFERRED,
7499     note                 TEXT,
7500     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7501     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7502     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7503 );
7504
7505 -- Trigger function to validate combination of org_unit and fund_code
7506
7507 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7508 RETURNS TRIGGER AS $$
7509 --
7510 DECLARE
7511 --
7512 dummy int := 0;
7513 --
7514 BEGIN
7515     SELECT
7516         1
7517     INTO
7518         dummy
7519     FROM
7520         acq.fund
7521     WHERE
7522         org = NEW.org
7523         AND code = NEW.fund_code
7524         LIMIT 1;
7525     --
7526     IF dummy = 1 then
7527         RETURN NEW;
7528     ELSE
7529         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7530     END IF;
7531 END;
7532 $$ LANGUAGE plpgsql;
7533
7534 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7535     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7536     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7537
7538 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7539 RETURNS TRIGGER AS $$
7540 DECLARE
7541 --
7542 total_percent numeric;
7543 --
7544 BEGIN
7545     SELECT
7546         sum( percent )
7547     INTO
7548         total_percent
7549     FROM
7550         acq.fund_allocation_percent AS fap
7551     WHERE
7552         fap.funding_source = NEW.funding_source;
7553     --
7554     IF total_percent > 100 THEN
7555         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7556             NEW.funding_source;
7557     ELSE
7558         RETURN NEW;
7559     END IF;
7560 END;
7561 $$ LANGUAGE plpgsql;
7562
7563 CREATE TRIGGER acqfap_limit_100_trig
7564     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7565     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7566
7567 -- Populate new table from acq.fund_allocation
7568
7569 INSERT INTO acq.fund_allocation_percent
7570 (
7571     funding_source,
7572     org,
7573     fund_code,
7574     percent,
7575     allocator,
7576     note,
7577     create_time
7578 )
7579     SELECT
7580         fa.funding_source,
7581         fund.org,
7582         fund.code,
7583         fa.percent,
7584         fa.allocator,
7585         fa.note,
7586         fa.create_time
7587     FROM
7588         acq.fund_allocation AS fa
7589             INNER JOIN acq.fund AS fund
7590                 ON ( fa.fund = fund.id )
7591     WHERE
7592         fa.percent is not null
7593     ORDER BY
7594         fund.org;
7595
7596 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7597
7598 -- Algorithm to apply to each funding source:
7599
7600 -- 1. Add up the credits.
7601 -- 2. Add up the percentages.
7602 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7603 --    fractional cents from the result.  This is the total amount to be allocated.
7604 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7605 --    fractional cents to get a preliminary amount.
7606 -- 5. Add up the preliminary amounts for all the allocations.
7607 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7608 --    number of residual cents (resulting from having dropped fractional cents) that
7609 --    must be distributed across the funds in order to make the total of the amounts
7610 --    match the total allocation.
7611 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7612 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7613 --    for each successive fund, until all the residual cents have been exhausted.
7614
7615 -- Result: the sum of the individual allocations now equals the total to be allocated,
7616 -- to the penny.  The individual amounts match the percentages as closely as possible,
7617 -- given the constraint that the total must match.
7618
7619 CREATE OR REPLACE FUNCTION acq.apply_percents()
7620 RETURNS VOID AS $$
7621 declare
7622 --
7623 tot              RECORD;
7624 fund             RECORD;
7625 tot_cents        INTEGER;
7626 src              INTEGER;
7627 id               INTEGER[];
7628 curr_id          INTEGER;
7629 pennies          NUMERIC[];
7630 curr_amount      NUMERIC;
7631 i                INTEGER;
7632 total_of_floors  INTEGER;
7633 total_percent    NUMERIC;
7634 total_allocation INTEGER;
7635 residue          INTEGER;
7636 --
7637 begin
7638         RAISE NOTICE 'Applying percents';
7639         FOR tot IN
7640                 SELECT
7641                         fsrc.funding_source,
7642                         sum( fsrc.amount ) AS total
7643                 FROM
7644                         acq.funding_source_credit AS fsrc
7645                 WHERE fsrc.funding_source IN
7646                         ( SELECT DISTINCT fa.funding_source
7647                           FROM acq.fund_allocation AS fa
7648                           WHERE fa.percent IS NOT NULL )
7649                 GROUP BY
7650                         fsrc.funding_source
7651         LOOP
7652                 tot_cents = floor( tot.total * 100 );
7653                 src = tot.funding_source;
7654                 RAISE NOTICE 'Funding source % total %',
7655                         src, tot_cents;
7656                 i := 0;
7657                 total_of_floors := 0;
7658                 total_percent := 0;
7659                 --
7660                 FOR fund in
7661                         SELECT
7662                                 fa.id,
7663                                 fa.percent,
7664                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7665                         FROM
7666                                 acq.fund_allocation AS fa
7667                         WHERE
7668                                 fa.funding_source = src
7669                                 AND fa.percent IS NOT NULL
7670                         ORDER BY
7671                                 mod( fa.percent * tot_cents / 100, 1 ),
7672                                 fa.fund,
7673                                 fa.id
7674                 LOOP
7675                         RAISE NOTICE '   %: %',
7676                                 fund.id,
7677                                 fund.floor_pennies;
7678                         i := i + 1;
7679                         id[i] = fund.id;
7680                         pennies[i] = fund.floor_pennies;
7681                         total_percent := total_percent + fund.percent;
7682                         total_of_floors := total_of_floors + pennies[i];
7683                 END LOOP;
7684                 total_allocation := floor( total_percent * tot_cents /100 );
7685                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7686                 residue := total_allocation - total_of_floors;
7687                 RAISE NOTICE 'Residue: %', residue;
7688                 --
7689                 -- Post the calculated amounts, revising as needed to
7690                 -- distribute the rounding error
7691                 --
7692                 WHILE i > 0 LOOP
7693                         IF residue > 0 THEN
7694                                 pennies[i] = pennies[i] + 1;
7695                                 residue := residue - 1;
7696                         END IF;
7697                         --
7698                         -- Post amount
7699                         --
7700                         curr_id     := id[i];
7701                         curr_amount := trunc( pennies[i] / 100, 2 );
7702                         --
7703                         UPDATE
7704                                 acq.fund_allocation AS fa
7705                         SET
7706                                 amount = curr_amount,
7707                                 percent = NULL
7708                         WHERE
7709                                 fa.id = curr_id;
7710                         --
7711                         RAISE NOTICE '   ID % and amount %',
7712                                 curr_id,
7713                                 curr_amount;
7714                         i = i - 1;
7715                 END LOOP;
7716         END LOOP;
7717 end;
7718 $$ LANGUAGE 'plpgsql';
7719
7720 -- Run the temporary function
7721
7722 select * from acq.apply_percents();
7723
7724 -- Drop the temporary function now that we're done with it
7725
7726 DROP FUNCTION IF EXISTS acq.apply_percents();
7727
7728 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7729
7730 -- If the following step fails, it's probably because there are still some non-null percent values in
7731 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7732 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7733 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7734 -- slipped in afterwards.
7735
7736 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7737 -- procedure acq.apply_percents() as defined above.
7738
7739 ALTER TABLE acq.fund_allocation
7740 ALTER COLUMN amount SET NOT NULL;
7741
7742 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7743     SELECT  fund,
7744             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7745     FROM acq.fund_allocation a
7746          JOIN acq.fund f ON (a.fund = f.id)
7747          JOIN acq.funding_source s ON (a.funding_source = s.id)
7748     GROUP BY 1;
7749
7750 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7751     SELECT  funding_source,
7752             SUM(a.amount)::NUMERIC(100,2) AS amount
7753     FROM  acq.fund_allocation a
7754     GROUP BY 1;
7755
7756 ALTER TABLE acq.fund_allocation
7757 DROP COLUMN percent;
7758
7759 CREATE TABLE asset.copy_location_order
7760 (
7761         id              SERIAL           PRIMARY KEY,
7762         location        INT              NOT NULL
7763                                              REFERENCES asset.copy_location
7764                                              ON DELETE CASCADE
7765                                              DEFERRABLE INITIALLY DEFERRED,
7766         org             INT              NOT NULL
7767                                              REFERENCES actor.org_unit
7768                                              ON DELETE CASCADE
7769                                              DEFERRABLE INITIALLY DEFERRED,
7770         position        INT              NOT NULL DEFAULT 0,
7771         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7772 );
7773
7774 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7775
7776 -- If you ran this before its most recent incarnation:
7777 -- delete from config.upgrade_log where version = '0328';
7778 -- alter table money.credit_card_payment drop column cc_name;
7779
7780 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7781 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7782
7783 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$
7784 DECLARE
7785     current_group    permission.grp_tree%ROWTYPE;
7786     user_object    actor.usr%ROWTYPE;
7787     item_object    asset.copy%ROWTYPE;
7788     cn_object    asset.call_number%ROWTYPE;
7789     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7790     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7791     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7792 BEGIN
7793     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7794     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7795     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7796     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7797     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7798
7799     LOOP 
7800         -- for each potential matchpoint for this ou and group ...
7801         FOR current_mp IN
7802             SELECT  m.*
7803               FROM  config.circ_matrix_matchpoint m
7804                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
7805                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
7806               WHERE m.grp = current_group.id
7807                     AND m.active
7808                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
7809                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
7810               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
7811                     CASE WHEN m.copy_owning_lib IS NOT NULL
7812                         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 )
7813                         ELSE 0
7814                     END +
7815                     CASE WHEN m.copy_circ_lib IS NOT NULL
7816                         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 )
7817                         ELSE 0
7818                     END +
7819                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
7820                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
7821                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
7822                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
7823                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
7824                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
7825                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
7826                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
7827                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
7828
7829             IF current_mp.is_renewal IS NOT NULL THEN
7830                 CONTINUE WHEN current_mp.is_renewal <> renewal;
7831             END IF;
7832
7833             IF current_mp.circ_modifier IS NOT NULL THEN
7834                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
7835             END IF;
7836
7837             IF current_mp.marc_type IS NOT NULL THEN
7838                 IF item_object.circ_as_type IS NOT NULL THEN
7839                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
7840                 ELSE
7841                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
7842                 END IF;
7843             END IF;
7844
7845             IF current_mp.marc_form IS NOT NULL THEN
7846                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
7847             END IF;
7848
7849             IF current_mp.marc_vr_format IS NOT NULL THEN
7850                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
7851             END IF;
7852
7853             IF current_mp.ref_flag IS NOT NULL THEN
7854                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
7855             END IF;
7856
7857             IF current_mp.juvenile_flag IS NOT NULL THEN
7858                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
7859             END IF;
7860
7861             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
7862                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
7863             END IF;
7864
7865             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
7866                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
7867             END IF;
7868
7869
7870             -- everything was undefined or matched
7871             matchpoint = current_mp;
7872
7873             EXIT WHEN matchpoint.id IS NOT NULL;
7874         END LOOP;
7875
7876         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
7877
7878         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
7879     END LOOP;
7880
7881     RETURN matchpoint;
7882 END;
7883 $func$ LANGUAGE plpgsql;
7884
7885 CREATE TYPE action.hold_stats AS (
7886     hold_count              INT,
7887     copy_count              INT,
7888     available_count         INT,
7889     total_copy_ratio        FLOAT,
7890     available_copy_ratio    FLOAT
7891 );
7892
7893 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
7894 DECLARE
7895     output          action.hold_stats%ROWTYPE;
7896     hold_count      INT := 0;
7897     copy_count      INT := 0;
7898     available_count INT := 0;
7899     hold_map_data   RECORD;
7900 BEGIN
7901
7902     output.hold_count := 0;
7903     output.copy_count := 0;
7904     output.available_count := 0;
7905
7906     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
7907       FROM  action.hold_copy_map m
7908             JOIN action.hold_request h ON (m.hold = h.id)
7909       WHERE m.target_copy = copy_id
7910             AND NOT h.frozen;
7911
7912     output.hold_count := hold_count;
7913
7914     IF output.hold_count > 0 THEN
7915         FOR hold_map_data IN
7916             SELECT  DISTINCT m.target_copy,
7917                     acp.status
7918               FROM  action.hold_copy_map m
7919                     JOIN asset.copy acp ON (m.target_copy = acp.id)
7920                     JOIN action.hold_request h ON (m.hold = h.id)
7921               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
7922         LOOP
7923             output.copy_count := output.copy_count + 1;
7924             IF hold_map_data.status IN (0,7,12) THEN
7925                 output.available_count := output.available_count + 1;
7926             END IF;
7927         END LOOP;
7928         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
7929         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
7930
7931     END IF;
7932
7933     RETURN output;
7934
7935 END;
7936 $func$ LANGUAGE PLPGSQL;
7937
7938 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
7939 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
7940
7941 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
7942
7943 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7944 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
7945
7946 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
7947     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
7948     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
7949     copy_owning_lib
7950 );
7951
7952 -- Return the correct fail_part when the item can't be found
7953 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$
7954 DECLARE
7955     user_object        actor.usr%ROWTYPE;
7956     standing_penalty    config.standing_penalty%ROWTYPE;
7957     item_object        asset.copy%ROWTYPE;
7958     item_status_object    config.copy_status%ROWTYPE;
7959     item_location_object    asset.copy_location%ROWTYPE;
7960     result            action.matrix_test_result;
7961     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
7962     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
7963     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
7964     hold_ratio          action.hold_stats%ROWTYPE;
7965     penalty_type         TEXT;
7966     tmp_grp         INT;
7967     items_out        INT;
7968     context_org_list        INT[];
7969     done            BOOL := FALSE;
7970 BEGIN
7971     result.success := TRUE;
7972
7973     -- Fail if the user is BARRED
7974     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7975
7976     -- Fail if we couldn't find the user 
7977     IF user_object.id IS NULL THEN
7978         result.fail_part := 'no_user';
7979         result.success := FALSE;
7980         done := TRUE;
7981         RETURN NEXT result;
7982         RETURN;
7983     END IF;
7984
7985     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7986
7987     -- Fail if we couldn't find the item 
7988     IF item_object.id IS NULL THEN
7989         result.fail_part := 'no_item';
7990         result.success := FALSE;
7991         done := TRUE;
7992         RETURN NEXT result;
7993         RETURN;
7994     END IF;
7995
7996     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
7997     result.matchpoint := circ_test.id;
7998
7999     -- Fail if we couldn't find a matchpoint
8000     IF result.matchpoint IS NULL THEN
8001         result.fail_part := 'no_matchpoint';
8002         result.success := FALSE;
8003         done := TRUE;
8004         RETURN NEXT result;
8005     END IF;
8006
8007     IF user_object.barred IS TRUE THEN
8008         result.fail_part := 'actor.usr.barred';
8009         result.success := FALSE;
8010         done := TRUE;
8011         RETURN NEXT result;
8012     END IF;
8013
8014     -- Fail if the item can't circulate
8015     IF item_object.circulate IS FALSE THEN
8016         result.fail_part := 'asset.copy.circulate';
8017         result.success := FALSE;
8018         done := TRUE;
8019         RETURN NEXT result;
8020     END IF;
8021
8022     -- Fail if the item isn't in a circulateable status on a non-renewal
8023     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
8024         result.fail_part := 'asset.copy.status';
8025         result.success := FALSE;
8026         done := TRUE;
8027         RETURN NEXT result;
8028     ELSIF renewal AND item_object.status <> 1 THEN
8029         result.fail_part := 'asset.copy.status';
8030         result.success := FALSE;
8031         done := TRUE;
8032         RETURN NEXT result;
8033     END IF;
8034
8035     -- Fail if the item can't circulate because of the shelving location
8036     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8037     IF item_location_object.circulate IS FALSE THEN
8038         result.fail_part := 'asset.copy_location.circulate';
8039         result.success := FALSE;
8040         done := TRUE;
8041         RETURN NEXT result;
8042     END IF;
8043
8044     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
8045
8046     -- Fail if the test is set to hard non-circulating
8047     IF circ_test.circulate IS FALSE THEN
8048         result.fail_part := 'config.circ_matrix_test.circulate';
8049         result.success := FALSE;
8050         done := TRUE;
8051         RETURN NEXT result;
8052     END IF;
8053
8054     -- Fail if the total copy-hold ratio is too low
8055     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
8056         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8057         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
8058             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8059             result.success := FALSE;
8060             done := TRUE;
8061             RETURN NEXT result;
8062         END IF;
8063     END IF;
8064
8065     -- Fail if the available copy-hold ratio is too low
8066     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
8067         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8068         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
8069             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8070             result.success := FALSE;
8071             done := TRUE;
8072             RETURN NEXT result;
8073         END IF;
8074     END IF;
8075
8076     IF renewal THEN
8077         penalty_type = '%RENEW%';
8078     ELSE
8079         penalty_type = '%CIRC%';
8080     END IF;
8081
8082     FOR standing_penalty IN
8083         SELECT  DISTINCT csp.*
8084           FROM  actor.usr_standing_penalty usp
8085                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8086           WHERE usr = match_user
8087                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8088                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8089                 AND csp.block_list LIKE penalty_type LOOP
8090
8091         result.fail_part := standing_penalty.name;
8092         result.success := FALSE;
8093         done := TRUE;
8094         RETURN NEXT result;
8095     END LOOP;
8096
8097     -- Fail if the user has too many items with specific circ_modifiers checked out
8098     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
8099         SELECT  INTO items_out COUNT(*)
8100           FROM  action.circulation circ
8101             JOIN asset.copy cp ON (cp.id = circ.target_copy)
8102           WHERE circ.usr = match_user
8103                AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
8104             AND circ.checkin_time IS NULL
8105             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8106             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);
8107         IF items_out >= out_by_circ_mod.items_out THEN
8108             result.fail_part := 'config.circ_matrix_circ_mod_test';
8109             result.success := FALSE;
8110             done := TRUE;
8111             RETURN NEXT result;
8112         END IF;
8113     END LOOP;
8114
8115     -- If we passed everything, return the successful matchpoint id
8116     IF NOT done THEN
8117         RETURN NEXT result;
8118     END IF;
8119
8120     RETURN;
8121 END;
8122 $func$ LANGUAGE plpgsql;
8123
8124 CREATE TABLE config.remote_account (
8125     id          SERIAL  PRIMARY KEY,
8126     label       TEXT    NOT NULL,
8127     host        TEXT    NOT NULL,   -- name or IP, :port optional
8128     username    TEXT,               -- optional, since we could default to $USER
8129     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
8130     account     TEXT,               -- aka profile or FTP "account" command
8131     path        TEXT,               -- aka directory
8132     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8133     last_activity TIMESTAMP WITH TIME ZONE
8134 );
8135
8136 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8137     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8138     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8139         vendcode    TEXT,
8140         vendacct    TEXT
8141
8142 ) INHERITS (config.remote_account);
8143
8144 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8145
8146 CREATE TABLE acq.claim_type (
8147         id             SERIAL           PRIMARY KEY,
8148         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8149                                                  DEFERRABLE INITIALLY DEFERRED,
8150         code           TEXT             NOT NULL,
8151         description    TEXT             NOT NULL,
8152         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8153 );
8154
8155 CREATE TABLE acq.claim (
8156         id             SERIAL           PRIMARY KEY,
8157         type           INT              NOT NULL REFERENCES acq.claim_type
8158                                                  DEFERRABLE INITIALLY DEFERRED,
8159         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8160                                                  DEFERRABLE INITIALLY DEFERRED
8161 );
8162
8163 CREATE TABLE acq.claim_policy (
8164         id              SERIAL       PRIMARY KEY,
8165         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8166                                      DEFERRABLE INITIALLY DEFERRED,
8167         name            TEXT         NOT NULL,
8168         description     TEXT         NOT NULL,
8169         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8170 );
8171
8172 -- Add a san column for EDI. 
8173 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8174
8175 ALTER TABLE acq.provider ADD COLUMN san INT;
8176
8177 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8178
8179 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8180 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8181
8182 ALTER TABLE acq.provider
8183         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8184
8185 ALTER TABLE acq.provider
8186         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8187
8188 ALTER TABLE acq.provider
8189         ADD COLUMN url TEXT;
8190
8191 ALTER TABLE acq.provider
8192         ADD COLUMN email TEXT;
8193
8194 ALTER TABLE acq.provider
8195         ADD COLUMN phone TEXT;
8196
8197 ALTER TABLE acq.provider
8198         ADD COLUMN fax_phone TEXT;
8199
8200 ALTER TABLE acq.provider
8201         ADD COLUMN default_claim_policy INT
8202                 REFERENCES acq.claim_policy
8203                 DEFERRABLE INITIALLY DEFERRED;
8204
8205 ALTER TABLE action.transit_copy
8206 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8207                                                          DEFERRABLE INITIALLY DEFERRED;
8208
8209 DROP SCHEMA IF EXISTS booking CASCADE;
8210
8211 CREATE SCHEMA booking;
8212
8213 CREATE TABLE booking.resource_type (
8214         id             SERIAL          PRIMARY KEY,
8215         name           TEXT            NOT NULL,
8216         fine_interval  INTERVAL,
8217         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
8218         owner          INT             NOT NULL
8219                                        REFERENCES actor.org_unit( id )
8220                                        DEFERRABLE INITIALLY DEFERRED,
8221         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
8222         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
8223     record         BIGINT          REFERENCES biblio.record_entry (id)
8224                                        DEFERRABLE INITIALLY DEFERRED,
8225     max_fine       NUMERIC(8,2),
8226     elbow_room     INTERVAL,
8227     CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
8228 );
8229
8230 CREATE TABLE booking.resource (
8231         id             SERIAL           PRIMARY KEY,
8232         owner          INT              NOT NULL
8233                                         REFERENCES actor.org_unit(id)
8234                                         DEFERRABLE INITIALLY DEFERRED,
8235         type           INT              NOT NULL
8236                                         REFERENCES booking.resource_type(id)
8237                                         DEFERRABLE INITIALLY DEFERRED,
8238         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
8239         barcode        TEXT             NOT NULL,
8240         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
8241         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8242         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8243         CONSTRAINT br_unique UNIQUE (owner, barcode)
8244 );
8245
8246 -- For non-catalog items: hijack barcode for name/description
8247
8248 CREATE TABLE booking.resource_attr (
8249         id              SERIAL          PRIMARY KEY,
8250         owner           INT             NOT NULL
8251                                         REFERENCES actor.org_unit(id)
8252                                         DEFERRABLE INITIALLY DEFERRED,
8253         name            TEXT            NOT NULL,
8254         resource_type   INT             NOT NULL
8255                                         REFERENCES booking.resource_type(id)
8256                                         ON DELETE CASCADE
8257                                         DEFERRABLE INITIALLY DEFERRED,
8258         required        BOOLEAN         NOT NULL DEFAULT FALSE,
8259         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
8260 );
8261
8262 CREATE TABLE booking.resource_attr_value (
8263         id               SERIAL         PRIMARY KEY,
8264         owner            INT            NOT NULL
8265                                         REFERENCES actor.org_unit(id)
8266                                         DEFERRABLE INITIALLY DEFERRED,
8267         attr             INT            NOT NULL
8268                                         REFERENCES booking.resource_attr(id)
8269                                         DEFERRABLE INITIALLY DEFERRED,
8270         valid_value      TEXT           NOT NULL,
8271         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
8272 );
8273
8274 CREATE TABLE booking.resource_attr_map (
8275         id               SERIAL         PRIMARY KEY,
8276         resource         INT            NOT NULL
8277                                         REFERENCES booking.resource(id)
8278                                         ON DELETE CASCADE
8279                                         DEFERRABLE INITIALLY DEFERRED,
8280         resource_attr    INT            NOT NULL
8281                                         REFERENCES booking.resource_attr(id)
8282                                         ON DELETE CASCADE
8283                                         DEFERRABLE INITIALLY DEFERRED,
8284         value            INT            NOT NULL
8285                                         REFERENCES booking.resource_attr_value(id)
8286                                         DEFERRABLE INITIALLY DEFERRED,
8287         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
8288 );
8289
8290 CREATE TABLE booking.reservation (
8291         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
8292         start_time       TIMESTAMPTZ,
8293         end_time         TIMESTAMPTZ,
8294         capture_time     TIMESTAMPTZ,
8295         cancel_time      TIMESTAMPTZ,
8296         pickup_time      TIMESTAMPTZ,
8297         return_time      TIMESTAMPTZ,
8298         booking_interval INTERVAL,
8299         fine_interval    INTERVAL,
8300         fine_amount      DECIMAL(8,2),
8301         target_resource_type  INT       NOT NULL
8302                                         REFERENCES booking.resource_type(id)
8303                                         ON DELETE CASCADE
8304                                         DEFERRABLE INITIALLY DEFERRED,
8305         target_resource  INT            REFERENCES booking.resource(id)
8306                                         ON DELETE CASCADE
8307                                         DEFERRABLE INITIALLY DEFERRED,
8308         current_resource INT            REFERENCES booking.resource(id)
8309                                         ON DELETE CASCADE
8310                                         DEFERRABLE INITIALLY DEFERRED,
8311         request_lib      INT            NOT NULL
8312                                         REFERENCES actor.org_unit(id)
8313                                         DEFERRABLE INITIALLY DEFERRED,
8314         pickup_lib       INT            REFERENCES actor.org_unit(id)
8315                                         DEFERRABLE INITIALLY DEFERRED,
8316         capture_staff    INT            REFERENCES actor.usr(id)
8317                                         DEFERRABLE INITIALLY DEFERRED,
8318     max_fine         NUMERIC(8,2)
8319 ) INHERITS (money.billable_xact);
8320
8321 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
8322
8323 ALTER TABLE booking.reservation
8324         ADD CONSTRAINT booking_reservation_usr_fkey
8325         FOREIGN KEY (usr) REFERENCES actor.usr (id)
8326         DEFERRABLE INITIALLY DEFERRED;
8327
8328 CREATE TABLE booking.reservation_attr_value_map (
8329         id               SERIAL         PRIMARY KEY,
8330         reservation      INT            NOT NULL
8331                                         REFERENCES booking.reservation(id)
8332                                         ON DELETE CASCADE
8333                                         DEFERRABLE INITIALLY DEFERRED,
8334         attr_value       INT            NOT NULL
8335                                         REFERENCES booking.resource_attr_value(id)
8336                                         ON DELETE CASCADE
8337                                         DEFERRABLE INITIALLY DEFERRED,
8338         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
8339 );
8340
8341 -- represents a circ chain summary
8342 CREATE TYPE action.circ_chain_summary AS (
8343     num_circs INTEGER,
8344     start_time TIMESTAMP WITH TIME ZONE,
8345     checkout_workstation TEXT,
8346     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8347     last_stop_fines TEXT,
8348     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8349     last_renewal_workstation TEXT, -- NULL if no renewals
8350     last_checkin_workstation TEXT,
8351     last_checkin_time TIMESTAMP WITH TIME ZONE,
8352     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8353 );
8354
8355 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8356 DECLARE
8357     tmp_circ action.circulation%ROWTYPE;
8358     circ_0 action.circulation%ROWTYPE;
8359 BEGIN
8360
8361     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8362
8363     IF tmp_circ IS NULL THEN
8364         RETURN NEXT tmp_circ;
8365     END IF;
8366     circ_0 := tmp_circ;
8367
8368     -- find the front of the chain
8369     WHILE TRUE LOOP
8370         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8371         IF tmp_circ IS NULL THEN
8372             EXIT;
8373         END IF;
8374         circ_0 := tmp_circ;
8375     END LOOP;
8376
8377     -- now send the circs to the caller, oldest to newest
8378     tmp_circ := circ_0;
8379     WHILE TRUE LOOP
8380         IF tmp_circ IS NULL THEN
8381             EXIT;
8382         END IF;
8383         RETURN NEXT tmp_circ;
8384         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8385     END LOOP;
8386
8387 END;
8388 $$ LANGUAGE 'plpgsql';
8389
8390 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8391
8392 DECLARE
8393
8394     -- first circ in the chain
8395     circ_0 action.circulation%ROWTYPE;
8396
8397     -- last circ in the chain
8398     circ_n action.circulation%ROWTYPE;
8399
8400     -- circ chain under construction
8401     chain action.circ_chain_summary;
8402     tmp_circ action.circulation%ROWTYPE;
8403
8404 BEGIN
8405     
8406     chain.num_circs := 0;
8407     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8408
8409         IF chain.num_circs = 0 THEN
8410             circ_0 := tmp_circ;
8411         END IF;
8412
8413         chain.num_circs := chain.num_circs + 1;
8414         circ_n := tmp_circ;
8415     END LOOP;
8416
8417     chain.start_time := circ_0.xact_start;
8418     chain.last_stop_fines := circ_n.stop_fines;
8419     chain.last_stop_fines_time := circ_n.stop_fines_time;
8420     chain.last_checkin_time := circ_n.checkin_time;
8421     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8422     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8423     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8424
8425     IF chain.num_circs > 1 THEN
8426         chain.last_renewal_time := circ_n.xact_start;
8427         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8428     END IF;
8429
8430     RETURN chain;
8431
8432 END;
8433 $$ LANGUAGE 'plpgsql';
8434
8435 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8436 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8437 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8438
8439 ALTER TABLE config.standing_penalty
8440         ADD COLUMN org_depth   INTEGER;
8441
8442 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8443 DECLARE
8444     user_object         actor.usr%ROWTYPE;
8445     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8446     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8447     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8448     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8449     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8450     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8451     tmp_grp             INT;
8452     items_overdue       INT;
8453     items_out           INT;
8454     context_org_list    INT[];
8455     current_fines        NUMERIC(8,2) := 0.0;
8456     tmp_fines            NUMERIC(8,2);
8457     tmp_groc            RECORD;
8458     tmp_circ            RECORD;
8459     tmp_org             actor.org_unit%ROWTYPE;
8460     tmp_penalty         config.standing_penalty%ROWTYPE;
8461     tmp_depth           INTEGER;
8462 BEGIN
8463     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8464
8465     -- Max fines
8466     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8467
8468     -- Fail if the user has a high fine balance
8469     LOOP
8470         tmp_grp := user_object.profile;
8471         LOOP
8472             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8473
8474             IF max_fines.threshold IS NULL THEN
8475                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8476             ELSE
8477                 EXIT;
8478             END IF;
8479
8480             IF tmp_grp IS NULL THEN
8481                 EXIT;
8482             END IF;
8483         END LOOP;
8484
8485         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8486             EXIT;
8487         END IF;
8488
8489         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8490
8491     END LOOP;
8492
8493     IF max_fines.threshold IS NOT NULL THEN
8494
8495         RETURN QUERY
8496             SELECT  *
8497               FROM  actor.usr_standing_penalty
8498               WHERE usr = match_user
8499                     AND org_unit = max_fines.org_unit
8500                     AND (stop_date IS NULL or stop_date > NOW())
8501                     AND standing_penalty = 1;
8502
8503         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8504
8505         SELECT  SUM(f.balance_owed) INTO current_fines
8506           FROM  money.materialized_billable_xact_summary f
8507                 JOIN (
8508                     SELECT  r.id
8509                       FROM  booking.reservation r
8510                       WHERE r.usr = match_user
8511                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8512                             AND xact_finish IS NULL
8513                                 UNION ALL
8514                     SELECT  g.id
8515                       FROM  money.grocery g
8516                       WHERE g.usr = match_user
8517                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8518                             AND xact_finish IS NULL
8519                                 UNION ALL
8520                     SELECT  circ.id
8521                       FROM  action.circulation circ
8522                       WHERE circ.usr = match_user
8523                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8524                             AND xact_finish IS NULL ) l USING (id);
8525
8526         IF current_fines >= max_fines.threshold THEN
8527             new_sp_row.usr := match_user;
8528             new_sp_row.org_unit := max_fines.org_unit;
8529             new_sp_row.standing_penalty := 1;
8530             RETURN NEXT new_sp_row;
8531         END IF;
8532     END IF;
8533
8534     -- Start over for max overdue
8535     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8536
8537     -- Fail if the user has too many overdue items
8538     LOOP
8539         tmp_grp := user_object.profile;
8540         LOOP
8541
8542             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8543
8544             IF max_overdue.threshold IS NULL THEN
8545                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8546             ELSE
8547                 EXIT;
8548             END IF;
8549
8550             IF tmp_grp IS NULL THEN
8551                 EXIT;
8552             END IF;
8553         END LOOP;
8554
8555         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8556             EXIT;
8557         END IF;
8558
8559         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8560
8561     END LOOP;
8562
8563     IF max_overdue.threshold IS NOT NULL THEN
8564
8565         RETURN QUERY
8566             SELECT  *
8567               FROM  actor.usr_standing_penalty
8568               WHERE usr = match_user
8569                     AND org_unit = max_overdue.org_unit
8570                     AND (stop_date IS NULL or stop_date > NOW())
8571                     AND standing_penalty = 2;
8572
8573         SELECT  INTO items_overdue COUNT(*)
8574           FROM  action.circulation circ
8575                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8576           WHERE circ.usr = match_user
8577             AND circ.checkin_time IS NULL
8578             AND circ.due_date < NOW()
8579             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8580
8581         IF items_overdue >= max_overdue.threshold::INT THEN
8582             new_sp_row.usr := match_user;
8583             new_sp_row.org_unit := max_overdue.org_unit;
8584             new_sp_row.standing_penalty := 2;
8585             RETURN NEXT new_sp_row;
8586         END IF;
8587     END IF;
8588
8589     -- Start over for max out
8590     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8591
8592     -- Fail if the user has too many checked out items
8593     LOOP
8594         tmp_grp := user_object.profile;
8595         LOOP
8596             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8597
8598             IF max_items_out.threshold IS NULL THEN
8599                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8600             ELSE
8601                 EXIT;
8602             END IF;
8603
8604             IF tmp_grp IS NULL THEN
8605                 EXIT;
8606             END IF;
8607         END LOOP;
8608
8609         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8610             EXIT;
8611         END IF;
8612
8613         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8614
8615     END LOOP;
8616
8617
8618     -- Fail if the user has too many items checked out
8619     IF max_items_out.threshold IS NOT NULL THEN
8620
8621         RETURN QUERY
8622             SELECT  *
8623               FROM  actor.usr_standing_penalty
8624               WHERE usr = match_user
8625                     AND org_unit = max_items_out.org_unit
8626                     AND (stop_date IS NULL or stop_date > NOW())
8627                     AND standing_penalty = 3;
8628
8629         SELECT  INTO items_out COUNT(*)
8630           FROM  action.circulation circ
8631                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8632           WHERE circ.usr = match_user
8633                 AND circ.checkin_time IS NULL
8634                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8635
8636            IF items_out >= max_items_out.threshold::INT THEN
8637             new_sp_row.usr := match_user;
8638             new_sp_row.org_unit := max_items_out.org_unit;
8639             new_sp_row.standing_penalty := 3;
8640             RETURN NEXT new_sp_row;
8641            END IF;
8642     END IF;
8643
8644     -- Start over for collections warning
8645     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8646
8647     -- Fail if the user has a collections-level fine balance
8648     LOOP
8649         tmp_grp := user_object.profile;
8650         LOOP
8651             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8652
8653             IF max_fines.threshold IS NULL THEN
8654                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8655             ELSE
8656                 EXIT;
8657             END IF;
8658
8659             IF tmp_grp IS NULL THEN
8660                 EXIT;
8661             END IF;
8662         END LOOP;
8663
8664         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8665             EXIT;
8666         END IF;
8667
8668         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8669
8670     END LOOP;
8671
8672     IF max_fines.threshold IS NOT NULL THEN
8673
8674         RETURN QUERY
8675             SELECT  *
8676               FROM  actor.usr_standing_penalty
8677               WHERE usr = match_user
8678                     AND org_unit = max_fines.org_unit
8679                     AND (stop_date IS NULL or stop_date > NOW())
8680                     AND standing_penalty = 4;
8681
8682         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8683
8684         SELECT  SUM(f.balance_owed) INTO current_fines
8685           FROM  money.materialized_billable_xact_summary f
8686                 JOIN (
8687                     SELECT  r.id
8688                       FROM  booking.reservation r
8689                       WHERE r.usr = match_user
8690                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8691                             AND r.xact_finish IS NULL
8692                                 UNION ALL
8693                     SELECT  g.id
8694                       FROM  money.grocery g
8695                       WHERE g.usr = match_user
8696                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8697                             AND g.xact_finish IS NULL
8698                                 UNION ALL
8699                     SELECT  circ.id
8700                       FROM  action.circulation circ
8701                       WHERE circ.usr = match_user
8702                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8703                             AND circ.xact_finish IS NULL ) l USING (id);
8704
8705         IF current_fines >= max_fines.threshold THEN
8706             new_sp_row.usr := match_user;
8707             new_sp_row.org_unit := max_fines.org_unit;
8708             new_sp_row.standing_penalty := 4;
8709             RETURN NEXT new_sp_row;
8710         END IF;
8711     END IF;
8712
8713     -- Start over for in collections
8714     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8715
8716     -- Remove the in-collections penalty if the user has paid down enough
8717     -- This penalty is different, because this code is not responsible for creating 
8718     -- new in-collections penalties, only for removing them
8719     LOOP
8720         tmp_grp := user_object.profile;
8721         LOOP
8722             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8723
8724             IF max_fines.threshold IS NULL THEN
8725                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8726             ELSE
8727                 EXIT;
8728             END IF;
8729
8730             IF tmp_grp IS NULL THEN
8731                 EXIT;
8732             END IF;
8733         END LOOP;
8734
8735         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8736             EXIT;
8737         END IF;
8738
8739         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8740
8741     END LOOP;
8742
8743     IF max_fines.threshold IS NOT NULL THEN
8744
8745         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8746
8747         -- first, see if the user had paid down to the threshold
8748         SELECT  SUM(f.balance_owed) INTO current_fines
8749           FROM  money.materialized_billable_xact_summary f
8750                 JOIN (
8751                     SELECT  r.id
8752                       FROM  booking.reservation r
8753                       WHERE r.usr = match_user
8754                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8755                             AND r.xact_finish IS NULL
8756                                 UNION ALL
8757                     SELECT  g.id
8758                       FROM  money.grocery g
8759                       WHERE g.usr = match_user
8760                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8761                             AND g.xact_finish IS NULL
8762                                 UNION ALL
8763                     SELECT  circ.id
8764                       FROM  action.circulation circ
8765                       WHERE circ.usr = match_user
8766                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8767                             AND circ.xact_finish IS NULL ) l USING (id);
8768
8769         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8770             -- patron has paid down enough
8771
8772             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8773
8774             IF tmp_penalty.org_depth IS NOT NULL THEN
8775
8776                 -- since this code is not responsible for applying the penalty, it can't 
8777                 -- guarantee the current context org will match the org at which the penalty 
8778                 --- was applied.  search up the org tree until we hit the configured penalty depth
8779                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8780                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8781
8782                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8783
8784                     RETURN QUERY
8785                         SELECT  *
8786                           FROM  actor.usr_standing_penalty
8787                           WHERE usr = match_user
8788                                 AND org_unit = tmp_org.id
8789                                 AND (stop_date IS NULL or stop_date > NOW())
8790                                 AND standing_penalty = 30;
8791
8792                     IF tmp_org.parent_ou IS NULL THEN
8793                         EXIT;
8794                     END IF;
8795
8796                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8797                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8798                 END LOOP;
8799
8800             ELSE
8801
8802                 -- no penalty depth is defined, look for exact matches
8803
8804                 RETURN QUERY
8805                     SELECT  *
8806                       FROM  actor.usr_standing_penalty
8807                       WHERE usr = match_user
8808                             AND org_unit = max_fines.org_unit
8809                             AND (stop_date IS NULL or stop_date > NOW())
8810                             AND standing_penalty = 30;
8811             END IF;
8812     
8813         END IF;
8814
8815     END IF;
8816
8817     RETURN;
8818 END;
8819 $func$ LANGUAGE plpgsql;
8820
8821 -- Create a default row in acq.fiscal_calendar
8822 -- Add a column in actor.org_unit to point to it
8823
8824 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8825
8826 ALTER TABLE actor.org_unit
8827 ADD COLUMN fiscal_calendar INT NOT NULL
8828         REFERENCES acq.fiscal_calendar( id )
8829         DEFERRABLE INITIALLY DEFERRED
8830         DEFAULT 1;
8831
8832 ALTER TABLE auditor.actor_org_unit_history
8833         ADD COLUMN fiscal_calendar INT;
8834
8835 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8836
8837 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8838
8839 ALTER TABLE acq.funding_source_credit
8840 ADD COLUMN deadline_date TIMESTAMPTZ;
8841
8842 ALTER TABLE acq.funding_source_credit
8843 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8844
8845 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8846
8847 CREATE TABLE acq.fund_transfer (
8848         id               SERIAL         PRIMARY KEY,
8849         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8850                                         DEFERRABLE INITIALLY DEFERRED,
8851         src_amount       NUMERIC        NOT NULL,
8852         dest_fund        INT            REFERENCES acq.fund( id )
8853                                         DEFERRABLE INITIALLY DEFERRED,
8854         dest_amount      NUMERIC,
8855         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
8856         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
8857                                         DEFERRABLE INITIALLY DEFERRED,
8858         note             TEXT,
8859     funding_source_credit INTEGER   NOT NULL
8860                                         REFERENCES acq.funding_source_credit(id)
8861                                         DEFERRABLE INITIALLY DEFERRED
8862 );
8863
8864 CREATE INDEX acqftr_usr_idx
8865 ON acq.fund_transfer( transfer_user );
8866
8867 COMMENT ON TABLE acq.fund_transfer IS $$
8868 /*
8869  * Copyright (C) 2009  Georgia Public Library Service
8870  * Scott McKellar <scott@esilibrary.com>
8871  *
8872  * Fund Transfer
8873  *
8874  * Each row represents the transfer of money from a source fund
8875  * to a destination fund.  There should be corresponding entries
8876  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
8877  * to record how much money moved from which fund to which other
8878  * fund.
8879  * 
8880  * The presence of two amount fields, rather than one, reflects
8881  * the possibility that the two funds are denominated in different
8882  * currencies.  If they use the same currency type, the two
8883  * amounts should be the same.
8884  *
8885  * ****
8886  *
8887  * This program is free software; you can redistribute it and/or
8888  * modify it under the terms of the GNU General Public License
8889  * as published by the Free Software Foundation; either version 2
8890  * of the License, or (at your option) any later version.
8891  *
8892  * This program is distributed in the hope that it will be useful,
8893  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8894  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
8895  * GNU General Public License for more details.
8896  */
8897 $$;
8898
8899 CREATE TABLE acq.claim_event_type (
8900         id             SERIAL           PRIMARY KEY,
8901         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8902                                                  DEFERRABLE INITIALLY DEFERRED,
8903         code           TEXT             NOT NULL,
8904         description    TEXT             NOT NULL,
8905         library_initiated BOOL          NOT NULL DEFAULT FALSE,
8906         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
8907 );
8908
8909 CREATE TABLE acq.claim_event (
8910         id             BIGSERIAL        PRIMARY KEY,
8911         type           INT              NOT NULL REFERENCES acq.claim_event_type
8912                                                  DEFERRABLE INITIALLY DEFERRED,
8913         claim          SERIAL           NOT NULL REFERENCES acq.claim
8914                                                  DEFERRABLE INITIALLY DEFERRED,
8915         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
8916         creator        INT              NOT NULL REFERENCES actor.usr
8917                                                  DEFERRABLE INITIALLY DEFERRED,
8918         note           TEXT
8919 );
8920
8921 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
8922
8923 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
8924         src_usr  IN INTEGER,
8925         dest_usr IN INTEGER
8926 ) RETURNS VOID AS $$
8927 DECLARE
8928         suffix TEXT;
8929         renamable_row RECORD;
8930 BEGIN
8931
8932         UPDATE actor.usr SET
8933                 active = FALSE,
8934                 card = NULL,
8935                 mailing_address = NULL,
8936                 billing_address = NULL
8937         WHERE id = src_usr;
8938
8939         -- acq.*
8940         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
8941         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
8942         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
8943         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
8944         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
8945         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
8946         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
8947
8948         -- Update with a rename to avoid collisions
8949         FOR renamable_row in
8950                 SELECT id, name
8951                 FROM   acq.picklist
8952                 WHERE  owner = src_usr
8953         LOOP
8954                 suffix := ' (' || src_usr || ')';
8955                 LOOP
8956                         BEGIN
8957                                 UPDATE  acq.picklist
8958                                 SET     owner = dest_usr, name = name || suffix
8959                                 WHERE   id = renamable_row.id;
8960                         EXCEPTION WHEN unique_violation THEN
8961                                 suffix := suffix || ' ';
8962                                 CONTINUE;
8963                         END;
8964                         EXIT;
8965                 END LOOP;
8966         END LOOP;
8967
8968         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
8969         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
8970         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
8971         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
8972         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
8973         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
8974         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
8975         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
8976
8977         -- action.*
8978         DELETE FROM action.circulation WHERE usr = src_usr;
8979         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
8980         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
8981         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
8982         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
8983         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
8984         DELETE FROM action.hold_request WHERE usr = src_usr;
8985         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
8986         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
8987         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
8988         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
8989         DELETE FROM action.survey_response WHERE usr = src_usr;
8990         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
8991
8992         -- actor.*
8993         DELETE FROM actor.card WHERE usr = src_usr;
8994         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
8995
8996         -- The following update is intended to avoid transient violations of a foreign
8997         -- key constraint, whereby actor.usr_address references itself.  It may not be
8998         -- necessary, but it does no harm.
8999         UPDATE actor.usr_address SET replaces = NULL
9000                 WHERE usr = src_usr AND replaces IS NOT NULL;
9001         DELETE FROM actor.usr_address WHERE usr = src_usr;
9002         DELETE FROM actor.usr_note WHERE usr = src_usr;
9003         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
9004         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
9005         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
9006         DELETE FROM actor.usr_setting WHERE usr = src_usr;
9007         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
9008         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
9009
9010         -- asset.*
9011         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
9012         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
9013         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
9014         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
9015         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
9016         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
9017
9018         -- auditor.*
9019         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
9020         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
9021         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
9022         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
9023         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
9024         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
9025         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
9026         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
9027
9028         -- biblio.*
9029         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
9030         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
9031         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
9032         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
9033
9034         -- container.*
9035         -- Update buckets with a rename to avoid collisions
9036         FOR renamable_row in
9037                 SELECT id, name
9038                 FROM   container.biblio_record_entry_bucket
9039                 WHERE  owner = src_usr
9040         LOOP
9041                 suffix := ' (' || src_usr || ')';
9042                 LOOP
9043                         BEGIN
9044                                 UPDATE  container.biblio_record_entry_bucket
9045                                 SET     owner = dest_usr, name = name || suffix
9046                                 WHERE   id = renamable_row.id;
9047                         EXCEPTION WHEN unique_violation THEN
9048                                 suffix := suffix || ' ';
9049                                 CONTINUE;
9050                         END;
9051                         EXIT;
9052                 END LOOP;
9053         END LOOP;
9054
9055         FOR renamable_row in
9056                 SELECT id, name
9057                 FROM   container.call_number_bucket
9058                 WHERE  owner = src_usr
9059         LOOP
9060                 suffix := ' (' || src_usr || ')';
9061                 LOOP
9062                         BEGIN
9063                                 UPDATE  container.call_number_bucket
9064                                 SET     owner = dest_usr, name = name || suffix
9065                                 WHERE   id = renamable_row.id;
9066                         EXCEPTION WHEN unique_violation THEN
9067                                 suffix := suffix || ' ';
9068                                 CONTINUE;
9069                         END;
9070                         EXIT;
9071                 END LOOP;
9072         END LOOP;
9073
9074         FOR renamable_row in
9075                 SELECT id, name
9076                 FROM   container.copy_bucket
9077                 WHERE  owner = src_usr
9078         LOOP
9079                 suffix := ' (' || src_usr || ')';
9080                 LOOP
9081                         BEGIN
9082                                 UPDATE  container.copy_bucket
9083                                 SET     owner = dest_usr, name = name || suffix
9084                                 WHERE   id = renamable_row.id;
9085                         EXCEPTION WHEN unique_violation THEN
9086                                 suffix := suffix || ' ';
9087                                 CONTINUE;
9088                         END;
9089                         EXIT;
9090                 END LOOP;
9091         END LOOP;
9092
9093         FOR renamable_row in
9094                 SELECT id, name
9095                 FROM   container.user_bucket
9096                 WHERE  owner = src_usr
9097         LOOP
9098                 suffix := ' (' || src_usr || ')';
9099                 LOOP
9100                         BEGIN
9101                                 UPDATE  container.user_bucket
9102                                 SET     owner = dest_usr, name = name || suffix
9103                                 WHERE   id = renamable_row.id;
9104                         EXCEPTION WHEN unique_violation THEN
9105                                 suffix := suffix || ' ';
9106                                 CONTINUE;
9107                         END;
9108                         EXIT;
9109                 END LOOP;
9110         END LOOP;
9111
9112         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
9113
9114         -- money.*
9115         DELETE FROM money.billable_xact WHERE usr = src_usr;
9116         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9117         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9118
9119         -- permission.*
9120         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9121         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9122         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9123         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9124
9125         -- reporter.*
9126         -- Update with a rename to avoid collisions
9127         BEGIN
9128                 FOR renamable_row in
9129                         SELECT id, name
9130                         FROM   reporter.output_folder
9131                         WHERE  owner = src_usr
9132                 LOOP
9133                         suffix := ' (' || src_usr || ')';
9134                         LOOP
9135                                 BEGIN
9136                                         UPDATE  reporter.output_folder
9137                                         SET     owner = dest_usr, name = name || suffix
9138                                         WHERE   id = renamable_row.id;
9139                                 EXCEPTION WHEN unique_violation THEN
9140                                         suffix := suffix || ' ';
9141                                         CONTINUE;
9142                                 END;
9143                                 EXIT;
9144                         END LOOP;
9145                 END LOOP;
9146         EXCEPTION WHEN undefined_table THEN
9147                 -- do nothing
9148         END;
9149
9150         BEGIN
9151                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9152         EXCEPTION WHEN undefined_table THEN
9153                 -- do nothing
9154         END;
9155
9156         -- Update with a rename to avoid collisions
9157         BEGIN
9158                 FOR renamable_row in
9159                         SELECT id, name
9160                         FROM   reporter.report_folder
9161                         WHERE  owner = src_usr
9162                 LOOP
9163                         suffix := ' (' || src_usr || ')';
9164                         LOOP
9165                                 BEGIN
9166                                         UPDATE  reporter.report_folder
9167                                         SET     owner = dest_usr, name = name || suffix
9168                                         WHERE   id = renamable_row.id;
9169                                 EXCEPTION WHEN unique_violation THEN
9170                                         suffix := suffix || ' ';
9171                                         CONTINUE;
9172                                 END;
9173                                 EXIT;
9174                         END LOOP;
9175                 END LOOP;
9176         EXCEPTION WHEN undefined_table THEN
9177                 -- do nothing
9178         END;
9179
9180         BEGIN
9181                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9182         EXCEPTION WHEN undefined_table THEN
9183                 -- do nothing
9184         END;
9185
9186         BEGIN
9187                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9188         EXCEPTION WHEN undefined_table THEN
9189                 -- do nothing
9190         END;
9191
9192         -- Update with a rename to avoid collisions
9193         BEGIN
9194                 FOR renamable_row in
9195                         SELECT id, name
9196                         FROM   reporter.template_folder
9197                         WHERE  owner = src_usr
9198                 LOOP
9199                         suffix := ' (' || src_usr || ')';
9200                         LOOP
9201                                 BEGIN
9202                                         UPDATE  reporter.template_folder
9203                                         SET     owner = dest_usr, name = name || suffix
9204                                         WHERE   id = renamable_row.id;
9205                                 EXCEPTION WHEN unique_violation THEN
9206                                         suffix := suffix || ' ';
9207                                         CONTINUE;
9208                                 END;
9209                                 EXIT;
9210                         END LOOP;
9211                 END LOOP;
9212         EXCEPTION WHEN undefined_table THEN
9213         -- do nothing
9214         END;
9215
9216         -- vandelay.*
9217         -- Update with a rename to avoid collisions
9218         FOR renamable_row in
9219                 SELECT id, name
9220                 FROM   vandelay.queue
9221                 WHERE  owner = src_usr
9222         LOOP
9223                 suffix := ' (' || src_usr || ')';
9224                 LOOP
9225                         BEGIN
9226                                 UPDATE  vandelay.queue
9227                                 SET     owner = dest_usr, name = name || suffix
9228                                 WHERE   id = renamable_row.id;
9229                         EXCEPTION WHEN unique_violation THEN
9230                                 suffix := suffix || ' ';
9231                                 CONTINUE;
9232                         END;
9233                         EXIT;
9234                 END LOOP;
9235         END LOOP;
9236
9237 END;
9238 $$ LANGUAGE plpgsql;
9239
9240 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9241 /**
9242  * Finds rows dependent on a given row in actor.usr and either deletes them
9243  * or reassigns them to a different user.
9244  */
9245 $$;
9246
9247 CREATE OR REPLACE FUNCTION actor.usr_delete(
9248         src_usr  IN INTEGER,
9249         dest_usr IN INTEGER
9250 ) RETURNS VOID AS $$
9251 DECLARE
9252         old_profile actor.usr.profile%type;
9253         old_home_ou actor.usr.home_ou%type;
9254         new_profile actor.usr.profile%type;
9255         new_home_ou actor.usr.home_ou%type;
9256         new_name    text;
9257         new_dob     actor.usr.dob%type;
9258 BEGIN
9259         SELECT
9260                 id || '-PURGED-' || now(),
9261                 profile,
9262                 home_ou,
9263                 dob
9264         INTO
9265                 new_name,
9266                 old_profile,
9267                 old_home_ou,
9268                 new_dob
9269         FROM
9270                 actor.usr
9271         WHERE
9272                 id = src_usr;
9273         --
9274         -- Quit if no such user
9275         --
9276         IF old_profile IS NULL THEN
9277                 RETURN;
9278         END IF;
9279         --
9280         perform actor.usr_purge_data( src_usr, dest_usr );
9281         --
9282         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9283         -- could assume that there is only one root.  Theoretically, someday, maybe,
9284         -- there could be multiple roots, so we take extra trouble to get the right ones.
9285         --
9286         SELECT
9287                 id
9288         INTO
9289                 new_profile
9290         FROM
9291                 permission.grp_ancestors( old_profile )
9292         WHERE
9293                 parent is null;
9294         --
9295         SELECT
9296                 id
9297         INTO
9298                 new_home_ou
9299         FROM
9300                 actor.org_unit_ancestors( old_home_ou )
9301         WHERE
9302                 parent_ou is null;
9303         --
9304         -- Truncate date of birth
9305         --
9306         IF new_dob IS NOT NULL THEN
9307                 new_dob := date_trunc( 'year', new_dob );
9308         END IF;
9309         --
9310         UPDATE
9311                 actor.usr
9312                 SET
9313                         card = NULL,
9314                         profile = new_profile,
9315                         usrname = new_name,
9316                         email = NULL,
9317                         passwd = random()::text,
9318                         standing = DEFAULT,
9319                         ident_type = 
9320                         (
9321                                 SELECT MIN( id )
9322                                 FROM config.identification_type
9323                         ),
9324                         ident_value = NULL,
9325                         ident_type2 = NULL,
9326                         ident_value2 = NULL,
9327                         net_access_level = DEFAULT,
9328                         photo_url = NULL,
9329                         prefix = NULL,
9330                         first_given_name = new_name,
9331                         second_given_name = NULL,
9332                         family_name = new_name,
9333                         suffix = NULL,
9334                         alias = NULL,
9335                         day_phone = NULL,
9336                         evening_phone = NULL,
9337                         other_phone = NULL,
9338                         mailing_address = NULL,
9339                         billing_address = NULL,
9340                         home_ou = new_home_ou,
9341                         dob = new_dob,
9342                         active = FALSE,
9343                         master_account = DEFAULT, 
9344                         super_user = DEFAULT,
9345                         barred = FALSE,
9346                         deleted = TRUE,
9347                         juvenile = DEFAULT,
9348                         usrgroup = 0,
9349                         claims_returned_count = DEFAULT,
9350                         credit_forward_balance = DEFAULT,
9351                         last_xact_id = DEFAULT,
9352                         alert_message = NULL,
9353                         create_date = now(),
9354                         expire_date = now()
9355         WHERE
9356                 id = src_usr;
9357 END;
9358 $$ LANGUAGE plpgsql;
9359
9360 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9361 /**
9362  * Logically deletes a user.  Removes personally identifiable information,
9363  * and purges associated data in other tables.
9364  */
9365 $$;
9366
9367 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9368
9369 ALTER TABLE acq.fund
9370 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9371
9372 ALTER TABLE acq.fund
9373         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9374
9375 -- A fund can't roll over if it doesn't propagate from one year to the next
9376
9377 ALTER TABLE acq.fund
9378         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9379         ( propagate OR NOT rollover );
9380
9381 ALTER TABLE acq.fund
9382         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9383
9384 ALTER TABLE acq.fund
9385     ADD COLUMN balance_warning_percent INT;
9386
9387 ALTER TABLE acq.fund
9388     ADD COLUMN balance_stop_percent INT;
9389
9390 CREATE VIEW acq.ordered_funding_source_credit AS
9391         SELECT
9392                 CASE WHEN deadline_date IS NULL THEN
9393                         2
9394                 ELSE
9395                         1
9396                 END AS sort_priority,
9397                 CASE WHEN deadline_date IS NULL THEN
9398                         effective_date
9399                 ELSE
9400                         deadline_date
9401                 END AS sort_date,
9402                 id,
9403                 funding_source,
9404                 amount,
9405                 note
9406         FROM
9407                 acq.funding_source_credit;
9408
9409 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9410 /*
9411  * Copyright (C) 2009  Georgia Public Library Service
9412  * Scott McKellar <scott@gmail.com>
9413  *
9414  * The acq.ordered_funding_source_credit view is a prioritized
9415  * ordering of funding source credits.  When ordered by the first
9416  * three columns, this view defines the order in which the various
9417  * credits are to be tapped for spending, subject to the allocations
9418  * in the acq.fund_allocation table.
9419  *
9420  * The first column reflects the principle that we should spend
9421  * money with deadlines before spending money without deadlines.
9422  *
9423  * The second column reflects the principle that we should spend the
9424  * oldest money first.  For money with deadlines, that means that we
9425  * spend first from the credit with the earliest deadline.  For
9426  * money without deadlines, we spend first from the credit with the
9427  * earliest effective date.  
9428  *
9429  * The third column is a tie breaker to ensure a consistent
9430  * ordering.
9431  *
9432  * ****
9433  *
9434  * This program is free software; you can redistribute it and/or
9435  * modify it under the terms of the GNU General Public License
9436  * as published by the Free Software Foundation; either version 2
9437  * of the License, or (at your option) any later version.
9438  *
9439  * This program is distributed in the hope that it will be useful,
9440  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9441  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9442  * GNU General Public License for more details.
9443  */
9444 $$;
9445
9446 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9447     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9448       FROM  money.materialized_billable_xact_summary m
9449             LEFT JOIN action.circulation c ON (c.id = m.id)
9450             LEFT JOIN money.grocery g ON (g.id = m.id)
9451             LEFT JOIN booking.reservation r ON (r.id = m.id);
9452
9453 CREATE TABLE config.marc21_rec_type_map (
9454     code        TEXT    PRIMARY KEY,
9455     type_val    TEXT    NOT NULL,
9456     blvl_val    TEXT    NOT NULL
9457 );
9458
9459 CREATE TABLE config.marc21_ff_pos_map (
9460     id          SERIAL  PRIMARY KEY,
9461     fixed_field TEXT    NOT NULL,
9462     tag         TEXT    NOT NULL,
9463     rec_type    TEXT    NOT NULL,
9464     start_pos   INT     NOT NULL,
9465     length      INT     NOT NULL,
9466     default_val TEXT    NOT NULL DEFAULT ' '
9467 );
9468
9469 CREATE TABLE config.marc21_physical_characteristic_type_map (
9470     ptype_key   TEXT    PRIMARY KEY,
9471     label       TEXT    NOT NULL -- I18N
9472 );
9473
9474 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9475     id          SERIAL  PRIMARY KEY,
9476     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9477     subfield    TEXT    NOT NULL,
9478     start_pos   INT     NOT NULL,
9479     length      INT     NOT NULL,
9480     label       TEXT    NOT NULL -- I18N
9481 );
9482
9483 CREATE TABLE config.marc21_physical_characteristic_value_map (
9484     id              SERIAL  PRIMARY KEY,
9485     value           TEXT    NOT NULL,
9486     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9487     label           TEXT    NOT NULL -- I18N
9488 );
9489
9490 ----------------------------------
9491 -- MARC21 record structure data --
9492 ----------------------------------
9493
9494 -- Record type map
9495 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9496 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9497 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9498 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9499 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9500 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9501 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9502 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9503
9504 ------ Physical Characteristics
9505
9506 -- Map
9507 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9508 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9509 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9510 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9511 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9512 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9513 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9514 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');
9515 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9516 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9517 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9518 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9519 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9520 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');
9521 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9522 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9523 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9524 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9525 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9526 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9527 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9528 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9529 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9530 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9531 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');
9532 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');
9533 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');
9534 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');
9535 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9536 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other photographic medium');
9537 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9538 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9539 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9540 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9541 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9542 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9543 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
9544 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');
9545 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9546 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');
9547 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
9548 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9549 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9550 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9551 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9552 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9553 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9554 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9555
9556 -- Electronic Resource
9557 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9558 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9559 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');
9560 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');
9561 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');
9562 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');
9563 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');
9564 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');
9565 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');
9566 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');
9567 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9568 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
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 ('c','d','3','1','Color');
9571 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');
9572 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');
9573 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9574 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Gray scale');
9575 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9576 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');
9577 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9578 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9579 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9580 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.');
9581 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.');
9582 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.');
9583 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.');
9584 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.');
9585 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');
9586 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.');
9587 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9588 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.');
9589 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9590 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
9591 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)');
9592 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
9593 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9594 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9595 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9596 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9597 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');
9598 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9599 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');
9600 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');
9601 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9602 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9603 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9604 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9605 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
9606 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9607 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
9608 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');
9609 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');
9610 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');
9611 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)');
9612 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9613 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');
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_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9616 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9617 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9618 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9619 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
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','l','13','1','Reformatting quality');
9622 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9623 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');
9624 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9625 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9626 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9627
9628 -- Globe
9629 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9630 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9631 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');
9632 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');
9633 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');
9634 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');
9635 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9636 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9637 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
9638 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');
9639 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9640 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9641 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9642 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9643 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9644 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9645 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9646 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9647 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9648 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9649 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9650 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9651 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9652 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9653 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');
9654 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9655 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9656
9657 -- Tactile Material
9658 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9659 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9660 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9661 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9662 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9663 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');
9664 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9665 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9666 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9667 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');
9668 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');
9669 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');
9670 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');
9671 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');
9672 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');
9673 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');
9674 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9675 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9676 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9677 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9678 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9679 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
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 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9684 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');
9685 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');
9686 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');
9687 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9688 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');
9689 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');
9690 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');
9691 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');
9692 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');
9693 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');
9694 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9695 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');
9696 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');
9697 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9698 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9699 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
9700 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');
9701 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');
9702 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');
9703 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9704 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9705
9706 -- Projected Graphic
9707 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9708 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9709 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');
9710 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9711 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');
9712 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');
9713 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9714 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
9715 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9716 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9717 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');
9718 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9719 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');
9720 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9721 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');
9722 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9723 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9724 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9725 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9726 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9727 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');
9728 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');
9729 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');
9730 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9731 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9732 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9733 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');
9734 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');
9735 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');
9736 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9737 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9738 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');
9739 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');
9740 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');
9741 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');
9742 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');
9743 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');
9744 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');
9745 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9746 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9747 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9748 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9749 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9750 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.');
9751 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.');
9752 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.');
9753 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.');
9754 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.');
9755 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.');
9756 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.');
9757 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.)');
9758 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.)');
9759 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.)');
9760 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.)');
9761 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9762 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'8 x 10 in. (21 x 26 cm.)');
9763 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.)');
9764 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.)');
9765 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.)');
9766 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9767 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9768 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9769 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9770 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9771 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9772 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');
9773 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');
9774 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9775 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9776 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9777
9778 -- Microform
9779 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9780 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9781 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');
9782 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');
9783 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');
9784 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');
9785 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9786 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');
9787 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9788 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9789 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9790 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9791 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9792 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9793 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9794 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9795 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
9796 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.');
9797 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.');
9798 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.');
9799 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9800 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.');
9801 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.)');
9802 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.)');
9803 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.)');
9804 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.)');
9805 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9806 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9807 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');
9808 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)');
9809 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)');
9810 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)');
9811 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)');
9812 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-)');
9813 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9814 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Reduction ratio varies');
9815 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9816 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');
9817 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9818 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9819 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9820 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9821 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9822 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');
9823 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9824 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9825 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9826 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');
9827 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9828 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9829 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
9830 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');
9831 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');
9832 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');
9833 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed generation');
9834 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9835 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9836 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');
9837 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');
9838 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');
9839 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');
9840 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');
9841 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');
9842 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');
9843 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');
9844 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');
9845 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9846 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9847
9848 -- Non-projected Graphic
9849 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9850 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
9851 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
9852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
9853 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
9854 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');
9855 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
9856 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
9857 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
9858 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
9859 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');
9860 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
9861 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');
9862 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9863 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9864 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
9865 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');
9866 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');
9867 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9868 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');
9869 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9870 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9871 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9872 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
9873 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9874 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');
9875 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');
9876 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9877 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9878 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9879 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9880 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9881 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');
9882 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9883 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9884 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9885 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9886 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9887 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9888 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9889 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9890 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
9891 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9892 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');
9893 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');
9894 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9895 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9896 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9897 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9898 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9899 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');
9900 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9901 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9902 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9903 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9904 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9905 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9906 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9907 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9908
9909 -- Motion Picture
9910 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
9911 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
9912 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');
9913 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');
9914 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');
9915 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9916 INSERT INTO config.marc21_physical_characteristic_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 ('m','d','3','1','Color');
9918 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');
9919 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9920 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');
9921 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9922 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9923 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9924 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
9925 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');
9926 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)');
9927 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
9928 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)');
9929 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');
9930 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');
9931 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9932 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9933 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');
9934 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');
9935 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');
9936 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9937 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
9938 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');
9939 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');
9940 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');
9941 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');
9942 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');
9943 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');
9944 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');
9945 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9946 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9947 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9948 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9949 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
9950 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.');
9951 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.');
9952 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.');
9953 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.');
9954 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.');
9955 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.');
9956 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.');
9957 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9958 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9959 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
9960 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9961 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
9962 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');
9963 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');
9964 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
9965 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9966 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9967 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
9968 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');
9969 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
9970 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
9971 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
9972 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');
9973 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');
9974 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');
9975 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');
9976 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9977
9978 -- Remote-sensing Image
9979 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
9980 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
9981 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9982 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
9983 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
9984 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
9985 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
9986 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');
9987 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9988 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9989 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
9990 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');
9991 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');
9992 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
9993 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');
9994 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9995 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
9996 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%');
9997 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%');
9998 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%');
9999 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%');
10000 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%');
10001 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%');
10002 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%');
10003 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%');
10004 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%');
10005 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%');
10006 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');
10007 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10008 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
10009 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
10010 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');
10011 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');
10012 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');
10013 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');
10014 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');
10015 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');
10016 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');
10017 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');
10018 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');
10019 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10020 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10021 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
10022 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
10023 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');
10024 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');
10025 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');
10026 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10027 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10028 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10029 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
10030 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
10031 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
10032 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10033 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10034 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
10035 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');
10036 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');
10037 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');
10038 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');
10039 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');
10040 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)');
10041 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');
10042 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
10043 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');
10044 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)');
10045 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)');
10046 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)');
10047 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');
10048 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');
10049 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');
10050 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');
10051 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');
10052 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');
10053 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');
10054 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');
10055 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');
10056 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');
10057 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');
10058 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');
10059 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');
10060 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');
10061 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');
10062 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');
10063 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');
10064 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');
10065 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');
10066 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');
10067 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');
10068 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)');
10069 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');
10070 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
10071 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
10072 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');
10073 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');
10074 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10075 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10076
10077 -- Sound Recording
10078 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
10079 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
10080 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');
10081 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
10082 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');
10083 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');
10084 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
10085 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');
10086 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');
10087 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10088 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');
10089 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10090 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
10091 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');
10092 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');
10093 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');
10094 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');
10095 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');
10096 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');
10097 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');
10098 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');
10099 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');
10100 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');
10101 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');
10102 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');
10103 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');
10104 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');
10105 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10106 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10107 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
10108 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10109 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
10110 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10111 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10112 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10113 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10114 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');
10115 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10116 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Coarse/standard');
10117 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10118 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10119 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10120 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.');
10121 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.');
10122 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.');
10123 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.');
10124 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.');
10125 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.');
10126 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.)');
10127 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.');
10128 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');
10129 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.');
10130 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.');
10131 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10132 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10133 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
10134 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.');
10135 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.');
10136 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');
10137 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.');
10138 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.');
10139 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10140 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10141 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
10142 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');
10143 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');
10144 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');
10145 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');
10146 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');
10147 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');
10148 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');
10149 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10150 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10151 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10152 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');
10153 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');
10154 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');
10155 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');
10156 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');
10157 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');
10158 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');
10159 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');
10160 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');
10161 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10162 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10163 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
10164 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');
10165 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');
10166 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');
10167 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');
10168 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10169 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10170
10171 -- Videorecording
10172 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10173 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10174 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10175 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10176 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10177 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10178 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10179 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10180 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10181 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');
10182 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10183 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10184 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');
10185 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10186 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10187 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10188 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10189 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10190 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');
10191 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10192 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');
10193 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10194 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10195 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10196 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10197 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');
10198 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');
10199 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');
10200 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');
10201 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.');
10202 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.');
10203 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10204 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10205 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10206 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');
10207 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');
10208 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');
10209 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10210 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10211 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');
10212 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');
10213 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');
10214 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');
10215 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');
10216 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');
10217 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');
10218 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10219 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10220 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10221 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10222 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10223 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.');
10224 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.');
10225 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.');
10226 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.');
10227 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.');
10228 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.');
10229 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10230 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10231 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10232 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10233 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10234 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');
10235 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');
10236 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10237 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10238 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10239
10240 -- Fixed Field position data -- 0-based!
10241 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10242 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10243 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10244 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10245 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10246 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10247 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10248 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10249 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10250 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10251 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10252 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10253 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10254 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10255 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10256 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10257 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10258 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10259 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10260 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10261 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10262 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10263 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10264 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10265 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10266 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10267 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10268 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10269 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10270 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10271 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10272 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10273 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10274 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10275 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10276 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10277 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10278 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10279 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10280 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10281 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10282 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10283 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10284 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10285 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10286 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10287 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10288 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10289 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10290 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10291 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10292 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10293 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10294 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10295 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10296 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10297 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10298 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10299 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10300 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10301 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10302 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10303 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10304 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10305 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10306 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10307 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10308 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10309 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10310 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10311 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10312 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10313 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10314 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10315 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10316 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10317 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10318 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10319 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10320 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10321 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10322 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10323 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10324 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10325 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10326 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10327 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10328 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10329 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10330 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10331 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10332 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10333 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10334 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10335 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10336 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10337 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10338 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10339 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10340 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10341 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10342 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10343 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10344 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10345 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10346 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10347 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10348 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10349 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10350 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10351 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10352 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10353 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10354 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10355 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10356 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10357 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10358 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10359 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10360 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10361 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10362 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10363 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10364 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10365 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10366 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10367 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10368 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10369 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10370 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10371 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10372 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10373 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10374 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10375 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');
10376 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');
10377 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10378 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10379 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10380 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10381 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10382 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10383 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10384 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10385 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10386 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10387
10388 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10389 DECLARE
10390         ldr         RECORD;
10391         tval        TEXT;
10392         tval_rec    RECORD;
10393         bval        TEXT;
10394         bval_rec    RECORD;
10395     retval      config.marc21_rec_type_map%ROWTYPE;
10396 BEGIN
10397     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10398
10399     IF ldr.id IS NULL THEN
10400         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10401         RETURN retval;
10402     END IF;
10403
10404     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10405     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10406
10407
10408     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10409     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10410
10411     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10412
10413     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10414
10415
10416     IF retval.code IS NULL THEN
10417         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10418     END IF;
10419
10420     RETURN retval;
10421 END;
10422 $func$ LANGUAGE PLPGSQL;
10423
10424 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10425 DECLARE
10426     rtype       TEXT;
10427     ff_pos      RECORD;
10428     tag_data    RECORD;
10429     val         TEXT;
10430 BEGIN
10431     rtype := (biblio.marc21_record_type( rid )).code;
10432     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10433         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10434             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10435             RETURN val;
10436         END LOOP;
10437         val := REPEAT( ff_pos.default_val, ff_pos.length );
10438         RETURN val;
10439     END LOOP;
10440
10441     RETURN NULL;
10442 END;
10443 $func$ LANGUAGE PLPGSQL;
10444
10445 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10446 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10447 DECLARE
10448     rowid   INT := 0;
10449     _007    RECORD;
10450     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10451     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10452     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10453     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10454 BEGIN
10455
10456     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10457
10458     IF _007.id IS NOT NULL THEN
10459         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10460
10461         IF ptype.ptype_key IS NOT NULL THEN
10462             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10463                 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 );
10464
10465                 IF pval.id IS NOT NULL THEN
10466                     rowid := rowid + 1;
10467                     retval.id := rowid;
10468                     retval.record := rid;
10469                     retval.ptype := ptype.ptype_key;
10470                     retval.subfield := psf.id;
10471                     retval.value := pval.id;
10472                     RETURN NEXT retval;
10473                 END IF;
10474
10475             END LOOP;
10476         END IF;
10477     END IF;
10478
10479     RETURN;
10480 END;
10481 $func$ LANGUAGE PLPGSQL;
10482
10483 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10484 DROP VIEW IF EXISTS money.open_usr_summary;
10485 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10486
10487 -- The view should supply defaults for numeric (amount) columns
10488 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10489     SELECT  xact.id,
10490         xact.usr,
10491         xact.xact_start,
10492         xact.xact_finish,
10493         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10494         credit.payment_ts AS last_payment_ts,
10495         credit.note AS last_payment_note,
10496         credit.payment_type AS last_payment_type,
10497         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10498         debit.billing_ts AS last_billing_ts,
10499         debit.note AS last_billing_note,
10500         debit.billing_type AS last_billing_type,
10501         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10502         p.relname AS xact_type
10503       FROM  money.billable_xact xact
10504         JOIN pg_class p ON xact.tableoid = p.oid
10505         LEFT JOIN (
10506             SELECT  billing.xact,
10507                 sum(billing.amount) AS amount,
10508                 max(billing.billing_ts) AS billing_ts,
10509                 last(billing.note) AS note,
10510                 last(billing.billing_type) AS billing_type
10511               FROM  money.billing
10512               WHERE billing.voided IS FALSE
10513               GROUP BY billing.xact
10514             ) debit ON xact.id = debit.xact
10515         LEFT JOIN (
10516             SELECT  payment_view.xact,
10517                 sum(payment_view.amount) AS amount,
10518                 max(payment_view.payment_ts) AS payment_ts,
10519                 last(payment_view.note) AS note,
10520                 last(payment_view.payment_type) AS payment_type
10521               FROM  money.payment_view
10522               WHERE payment_view.voided IS FALSE
10523               GROUP BY payment_view.xact
10524             ) credit ON xact.id = credit.xact
10525       ORDER BY debit.billing_ts, credit.payment_ts;
10526
10527 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10528     SELECT * FROM money.billable_xact_summary_location_view
10529     WHERE xact_finish IS NULL;
10530
10531 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10532     SELECT 
10533         usr,
10534         SUM(total_paid) AS total_paid,
10535         SUM(total_owed) AS total_owed,
10536         SUM(balance_owed) AS balance_owed
10537     FROM  money.materialized_billable_xact_summary
10538     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10539     GROUP BY usr;
10540
10541 CREATE OR REPLACE VIEW money.usr_summary AS
10542     SELECT 
10543         usr, 
10544         sum(total_paid) AS total_paid, 
10545         sum(total_owed) AS total_owed, 
10546         sum(balance_owed) AS balance_owed
10547     FROM money.materialized_billable_xact_summary
10548     GROUP BY usr;
10549
10550 CREATE OR REPLACE VIEW money.open_usr_summary AS
10551     SELECT 
10552         usr, 
10553         sum(total_paid) AS total_paid, 
10554         sum(total_owed) AS total_owed, 
10555         sum(balance_owed) AS balance_owed
10556     FROM money.materialized_billable_xact_summary
10557     WHERE xact_finish IS NULL
10558     GROUP BY usr;
10559
10560 -- 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;
10561
10562 CREATE TABLE config.biblio_fingerprint (
10563         id                      SERIAL  PRIMARY KEY,
10564         name            TEXT    NOT NULL, 
10565         xpath           TEXT    NOT NULL,
10566     first_word  BOOL    NOT NULL DEFAULT FALSE,
10567         format          TEXT    NOT NULL DEFAULT 'marcxml'
10568 );
10569
10570 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10571     VALUES (
10572         'Title',
10573         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10574             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10575             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10576             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10577             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10578         'marcxml'
10579     );
10580
10581 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10582     VALUES (
10583         'Author',
10584         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10585             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10586             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10587             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10588             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10589         'marcxml',
10590         TRUE
10591     );
10592
10593 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10594 DECLARE
10595     qual        INT;
10596     ldr         TEXT;
10597     tval        TEXT;
10598     tval_rec    RECORD;
10599     bval        TEXT;
10600     bval_rec    RECORD;
10601     type_map    RECORD;
10602     ff_pos      RECORD;
10603     ff_tag_data TEXT;
10604 BEGIN
10605
10606     IF marc IS NULL OR marc = '' THEN
10607         RETURN NULL;
10608     END IF;
10609
10610     -- First, the count of tags
10611     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10612
10613     -- now go through a bunch of pain to get the record type
10614     IF best_type IS NOT NULL THEN
10615         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10616
10617         IF ldr IS NOT NULL THEN
10618             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10619             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10620
10621
10622             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10623             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10624
10625             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10626
10627             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10628
10629             IF type_map.code IS NOT NULL THEN
10630                 IF best_type = type_map.code THEN
10631                     qual := qual + qual / 2;
10632                 END IF;
10633
10634                 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
10635                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10636                     IF ff_tag_data = best_lang THEN
10637                             qual := qual + 100;
10638                     END IF;
10639                 END LOOP;
10640             END IF;
10641         END IF;
10642     END IF;
10643
10644     -- Now look for some quality metrics
10645     -- DCL record?
10646     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10647         qual := qual + 10;
10648     END IF;
10649
10650     -- From OCLC?
10651     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10652         qual := qual + 10;
10653     END IF;
10654
10655     RETURN qual;
10656
10657 END;
10658 $func$ LANGUAGE PLPGSQL;
10659
10660 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10661 DECLARE
10662     idx     config.biblio_fingerprint%ROWTYPE;
10663     xfrm        config.xml_transform%ROWTYPE;
10664     prev_xfrm   TEXT;
10665     transformed_xml TEXT;
10666     xml_node    TEXT;
10667     xml_node_list   TEXT[];
10668     raw_text    TEXT;
10669     output_text TEXT := '';
10670 BEGIN
10671
10672     IF marc IS NULL OR marc = '' THEN
10673         RETURN NULL;
10674     END IF;
10675
10676     -- Loop over the indexing entries
10677     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10678
10679         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10680
10681         -- See if we can skip the XSLT ... it's expensive
10682         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10683             -- Can't skip the transform
10684             IF xfrm.xslt <> '---' THEN
10685                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10686             ELSE
10687                 transformed_xml := marc;
10688             END IF;
10689
10690             prev_xfrm := xfrm.name;
10691         END IF;
10692
10693         raw_text := COALESCE(
10694             naco_normalize(
10695                 ARRAY_TO_STRING(
10696                     oils_xpath(
10697                         '//text()',
10698                         (oils_xpath(
10699                             idx.xpath,
10700                             transformed_xml,
10701                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10702                         ))[1]
10703                     ),
10704                     ''
10705                 )
10706             ),
10707             ''
10708         );
10709
10710         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10711         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10712
10713         IF idx.first_word IS TRUE THEN
10714             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10715         END IF;
10716
10717         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10718
10719     END LOOP;
10720
10721     RETURN output_text;
10722
10723 END;
10724 $func$ LANGUAGE PLPGSQL;
10725
10726 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10727 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10728 BEGIN
10729
10730     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10731
10732     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10733         RETURN NEW;
10734     END IF;
10735
10736     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10737     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10738
10739     RETURN NEW;
10740
10741 END;
10742 $func$ LANGUAGE PLPGSQL;
10743
10744 CREATE TABLE config.internal_flag (
10745     name    TEXT    PRIMARY KEY,
10746     value   TEXT,
10747     enabled BOOL    NOT NULL DEFAULT FALSE
10748 );
10749 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10750 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10751 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10752 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10753 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10754 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10755 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10756 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10757 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10758 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10759
10760 CREATE TABLE authority.bib_linking (
10761     id          BIGSERIAL   PRIMARY KEY,
10762     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10763     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10764 );
10765 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10766 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10767
10768 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10769     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10770 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10771
10772 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10773     DELETE FROM authority.bib_linking WHERE bib = $1;
10774     INSERT INTO authority.bib_linking (bib, authority)
10775         SELECT  y.bib,
10776                 y.authority
10777           FROM (    SELECT  DISTINCT $1 AS bib,
10778                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10779                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10780                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10781                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10782     SELECT $1;
10783 $func$ LANGUAGE SQL;
10784
10785 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10786 BEGIN
10787     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10788     IF NOT FOUND THEN
10789         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10790     END IF;
10791     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)
10792         SELECT  bib_id,
10793                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10794                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10795                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10796                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10797                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10798                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10799                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10800                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10801                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10802                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10803                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10804                 (   SELECT  v.value
10805                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10806                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10807                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10808                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10809                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
10810                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
10811
10812     RETURN;
10813 END;
10814 $func$ LANGUAGE PLPGSQL;
10815
10816 CREATE TABLE config.metabib_class (
10817     name    TEXT    PRIMARY KEY,
10818     label   TEXT    NOT NULL UNIQUE
10819 );
10820
10821 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10822 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10823 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10824 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10825 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10826
10827 CREATE TABLE metabib.facet_entry (
10828         id              BIGSERIAL       PRIMARY KEY,
10829         source          BIGINT          NOT NULL,
10830         field           INT             NOT NULL,
10831         value           TEXT            NOT NULL
10832 );
10833
10834 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10835 DECLARE
10836     fclass          RECORD;
10837     ind_data        metabib.field_entry_template%ROWTYPE;
10838 BEGIN
10839     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10840     IF NOT FOUND THEN
10841         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10842             -- RAISE NOTICE 'Emptying out %', fclass.name;
10843             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10844         END LOOP;
10845         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10846     END IF;
10847
10848     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10849         IF ind_data.field < 0 THEN
10850             ind_data.field = -1 * ind_data.field;
10851             INSERT INTO metabib.facet_entry (field, source, value)
10852                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10853         ELSE
10854             EXECUTE $$
10855                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10856                     VALUES ($$ ||
10857                         quote_literal(ind_data.field) || $$, $$ ||
10858                         quote_literal(ind_data.source) || $$, $$ ||
10859                         quote_literal(ind_data.value) ||
10860                     $$);$$;
10861         END IF;
10862
10863     END LOOP;
10864
10865     RETURN;
10866 END;
10867 $func$ LANGUAGE PLPGSQL;
10868
10869 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
10870 DECLARE
10871     uris            TEXT[];
10872     uri_xml         TEXT;
10873     uri_label       TEXT;
10874     uri_href        TEXT;
10875     uri_use         TEXT;
10876     uri_owner       TEXT;
10877     uri_owner_id    INT;
10878     uri_id          INT;
10879     uri_cn_id       INT;
10880     uri_map_id      INT;
10881 BEGIN
10882
10883     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
10884     IF ARRAY_UPPER(uris,1) > 0 THEN
10885         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
10886             -- First we pull info out of the 856
10887             uri_xml     := uris[i];
10888
10889             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
10890             CONTINUE WHEN uri_href IS NULL;
10891
10892             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10893             CONTINUE WHEN uri_label IS NULL;
10894
10895             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
10896             CONTINUE WHEN uri_owner IS NULL;
10897
10898             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10899
10900             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
10901
10902             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
10903             CONTINUE WHEN NOT FOUND;
10904
10905             -- now we look for a matching uri
10906             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10907             IF NOT FOUND THEN -- create one
10908                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
10909                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10910             END IF;
10911
10912             -- we need a call number to link through
10913             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;
10914             IF NOT FOUND THEN
10915                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
10916                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
10917                 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;
10918             END IF;
10919
10920             -- now, link them if they're not already
10921             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
10922             IF NOT FOUND THEN
10923                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
10924             END IF;
10925
10926         END LOOP;
10927     END IF;
10928
10929     RETURN;
10930 END;
10931 $func$ LANGUAGE PLPGSQL;
10932
10933 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
10934 DECLARE
10935     source_count    INT;
10936     old_mr          BIGINT;
10937     tmp_mr          metabib.metarecord%ROWTYPE;
10938     deleted_mrs     BIGINT[];
10939 BEGIN
10940
10941     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
10942
10943     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
10944
10945         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
10946             old_mr := tmp_mr.id;
10947         ELSE
10948             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
10949             IF source_count = 0 THEN -- No other records
10950                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
10951                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
10952             END IF;
10953         END IF;
10954
10955     END LOOP;
10956
10957     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
10958         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
10959         IF old_mr IS NULL THEN -- nope, create one and grab its id
10960             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
10961             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
10962         ELSE -- indeed there is. update it with a null cache and recalcualated master record
10963             UPDATE  metabib.metarecord
10964               SET   mods = NULL,
10965                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
10966               WHERE id = old_mr;
10967         END IF;
10968     ELSE -- there was one we already attached to, update its mods cache and master_record
10969         UPDATE  metabib.metarecord
10970           SET   mods = NULL,
10971                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
10972           WHERE id = old_mr;
10973     END IF;
10974
10975     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
10976
10977     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
10978         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
10979     END IF;
10980
10981     RETURN old_mr;
10982
10983 END;
10984 $func$ LANGUAGE PLPGSQL;
10985
10986 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
10987 BEGIN
10988     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10989     IF NOT FOUND THEN
10990         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
10991     END IF;
10992     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
10993         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
10994
10995     RETURN;
10996 END;
10997 $func$ LANGUAGE PLPGSQL;
10998
10999 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
11000 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
11001 BEGIN
11002
11003     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
11004         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
11005         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
11006         RETURN NEW; -- and we're done
11007     END IF;
11008
11009     IF TG_OP = 'UPDATE' THEN -- re-ingest?
11010         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
11011
11012         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
11013             RETURN NEW;
11014         END IF;
11015     END IF;
11016
11017     -- Record authority linking
11018     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
11019     IF NOT FOUND THEN
11020         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11021     END IF;
11022
11023     -- Flatten and insert the mfr data
11024     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11025     IF NOT FOUND THEN
11026         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11027         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11028         IF NOT FOUND THEN
11029             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
11030         END IF;
11031     END IF;
11032
11033     -- Gather and insert the field entry data
11034     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11035
11036     -- Located URI magic
11037     IF TG_OP = 'INSERT' THEN
11038         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11039         IF NOT FOUND THEN
11040             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11041         END IF;
11042     ELSE
11043         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11044         IF NOT FOUND THEN
11045             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11046         END IF;
11047     END IF;
11048
11049     -- (re)map metarecord-bib linking
11050     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11051         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11052         IF NOT FOUND THEN
11053             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11054         END IF;
11055     ELSE -- we're doing an update, and we're not deleted, remap
11056         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11057         IF NOT FOUND THEN
11058             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11059         END IF;
11060     END IF;
11061
11062     RETURN NEW;
11063 END;
11064 $func$ LANGUAGE PLPGSQL;
11065
11066 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
11067 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 ();
11068
11069 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
11070
11071 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
11072 DECLARE
11073     xpath_list  TEXT[];
11074     select_list TEXT[];
11075     where_list  TEXT[];
11076     q           TEXT;
11077     out_record  RECORD;
11078     empty_test  RECORD;
11079 BEGIN
11080     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
11081  
11082     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
11083  
11084     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
11085         IF xpath_list[i] = 'null()' THEN
11086             select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
11087         ELSE
11088             select_list := ARRAY_APPEND(
11089                 select_list,
11090                 $sel$
11091                 EXPLODE_ARRAY(
11092                     COALESCE(
11093                         NULLIF(
11094                             oils_xpath(
11095                                 $sel$ ||
11096                                     quote_literal(
11097                                         CASE
11098                                             WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
11099                                             ELSE xpath_list[i] || '//text()'
11100                                         END
11101                                     ) ||
11102                                 $sel$,
11103                                 $sel$ || document_field || $sel$
11104                             ),
11105                            '{}'::TEXT[]
11106                         ),
11107                         '{NULL}'::TEXT[]
11108                     )
11109                 ) AS c_$sel$ || i
11110             );
11111             where_list := ARRAY_APPEND(
11112                 where_list,
11113                 'c_' || i || ' IS NOT NULL'
11114             );
11115         END IF;
11116     END LOOP;
11117  
11118     q := $q$
11119 SELECT * FROM (
11120     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11121 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
11122     -- RAISE NOTICE 'query: %', q;
11123  
11124     FOR out_record IN EXECUTE q LOOP
11125         RETURN NEXT out_record;
11126     END LOOP;
11127  
11128     RETURN;
11129 END;
11130 $func$ LANGUAGE PLPGSQL IMMUTABLE;
11131
11132 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11133 DECLARE
11134
11135     owning_lib      TEXT;
11136     circ_lib        TEXT;
11137     call_number     TEXT;
11138     copy_number     TEXT;
11139     status          TEXT;
11140     location        TEXT;
11141     circulate       TEXT;
11142     deposit         TEXT;
11143     deposit_amount  TEXT;
11144     ref             TEXT;
11145     holdable        TEXT;
11146     price           TEXT;
11147     barcode         TEXT;
11148     circ_modifier   TEXT;
11149     circ_as_type    TEXT;
11150     alert_message   TEXT;
11151     opac_visible    TEXT;
11152     pub_note        TEXT;
11153     priv_note       TEXT;
11154
11155     attr_def        RECORD;
11156     tmp_attr_set    RECORD;
11157     attr_set        vandelay.import_item%ROWTYPE;
11158
11159     xpath           TEXT;
11160
11161 BEGIN
11162
11163     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11164
11165     IF FOUND THEN
11166
11167         attr_set.definition := attr_def.id; 
11168     
11169         -- Build the combined XPath
11170     
11171         owning_lib :=
11172             CASE
11173                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11174                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11175                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11176             END;
11177     
11178         circ_lib :=
11179             CASE
11180                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11181                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11182                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11183             END;
11184     
11185         call_number :=
11186             CASE
11187                 WHEN attr_def.call_number IS NULL THEN 'null()'
11188                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11189                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11190             END;
11191     
11192         copy_number :=
11193             CASE
11194                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11195                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11196                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11197             END;
11198     
11199         status :=
11200             CASE
11201                 WHEN attr_def.status IS NULL THEN 'null()'
11202                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11203                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11204             END;
11205     
11206         location :=
11207             CASE
11208                 WHEN attr_def.location IS NULL THEN 'null()'
11209                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11210                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11211             END;
11212     
11213         circulate :=
11214             CASE
11215                 WHEN attr_def.circulate IS NULL THEN 'null()'
11216                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11217                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11218             END;
11219     
11220         deposit :=
11221             CASE
11222                 WHEN attr_def.deposit IS NULL THEN 'null()'
11223                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11224                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11225             END;
11226     
11227         deposit_amount :=
11228             CASE
11229                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11230                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11231                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11232             END;
11233     
11234         ref :=
11235             CASE
11236                 WHEN attr_def.ref IS NULL THEN 'null()'
11237                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11238                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11239             END;
11240     
11241         holdable :=
11242             CASE
11243                 WHEN attr_def.holdable IS NULL THEN 'null()'
11244                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11245                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11246             END;
11247     
11248         price :=
11249             CASE
11250                 WHEN attr_def.price IS NULL THEN 'null()'
11251                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11252                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11253             END;
11254     
11255         barcode :=
11256             CASE
11257                 WHEN attr_def.barcode IS NULL THEN 'null()'
11258                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11259                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11260             END;
11261     
11262         circ_modifier :=
11263             CASE
11264                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11265                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11266                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11267             END;
11268     
11269         circ_as_type :=
11270             CASE
11271                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11272                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11273                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11274             END;
11275     
11276         alert_message :=
11277             CASE
11278                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11279                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11280                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11281             END;
11282     
11283         opac_visible :=
11284             CASE
11285                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11286                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11287                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11288             END;
11289
11290         pub_note :=
11291             CASE
11292                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11293                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11294                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11295             END;
11296         priv_note :=
11297             CASE
11298                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11299                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11300                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11301             END;
11302     
11303     
11304         xpath := 
11305             owning_lib      || '|' || 
11306             circ_lib        || '|' || 
11307             call_number     || '|' || 
11308             copy_number     || '|' || 
11309             status          || '|' || 
11310             location        || '|' || 
11311             circulate       || '|' || 
11312             deposit         || '|' || 
11313             deposit_amount  || '|' || 
11314             ref             || '|' || 
11315             holdable        || '|' || 
11316             price           || '|' || 
11317             barcode         || '|' || 
11318             circ_modifier   || '|' || 
11319             circ_as_type    || '|' || 
11320             alert_message   || '|' || 
11321             pub_note        || '|' || 
11322             priv_note       || '|' || 
11323             opac_visible;
11324
11325         -- RAISE NOTICE 'XPath: %', xpath;
11326         
11327         FOR tmp_attr_set IN
11328                 SELECT  *
11329                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11330                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11331                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11332                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11333         LOOP
11334     
11335             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11336             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11337
11338             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11339             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11340     
11341             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11342             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11343             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11344     
11345             SELECT  id INTO attr_set.location
11346               FROM  asset.copy_location
11347               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11348                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11349     
11350             attr_set.circulate      :=
11351                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11352                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11353
11354             attr_set.deposit        :=
11355                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11356                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11357
11358             attr_set.holdable       :=
11359                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11360                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11361
11362             attr_set.opac_visible   :=
11363                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11364                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11365
11366             attr_set.ref            :=
11367                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11368                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11369     
11370             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11371             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11372             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11373     
11374             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11375             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11376             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11377             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11378             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11379             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11380             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11381             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11382     
11383             RETURN NEXT attr_set;
11384     
11385         END LOOP;
11386     
11387     END IF;
11388
11389     RETURN;
11390
11391 END;
11392 $$ LANGUAGE PLPGSQL;
11393
11394 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11395 DECLARE
11396     attr_def    BIGINT;
11397     item_data   vandelay.import_item%ROWTYPE;
11398 BEGIN
11399
11400     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11401
11402     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11403         INSERT INTO vandelay.import_item (
11404             record,
11405             definition,
11406             owning_lib,
11407             circ_lib,
11408             call_number,
11409             copy_number,
11410             status,
11411             location,
11412             circulate,
11413             deposit,
11414             deposit_amount,
11415             ref,
11416             holdable,
11417             price,
11418             barcode,
11419             circ_modifier,
11420             circ_as_type,
11421             alert_message,
11422             pub_note,
11423             priv_note,
11424             opac_visible
11425         ) VALUES (
11426             NEW.id,
11427             item_data.definition,
11428             item_data.owning_lib,
11429             item_data.circ_lib,
11430             item_data.call_number,
11431             item_data.copy_number,
11432             item_data.status,
11433             item_data.location,
11434             item_data.circulate,
11435             item_data.deposit,
11436             item_data.deposit_amount,
11437             item_data.ref,
11438             item_data.holdable,
11439             item_data.price,
11440             item_data.barcode,
11441             item_data.circ_modifier,
11442             item_data.circ_as_type,
11443             item_data.alert_message,
11444             item_data.pub_note,
11445             item_data.priv_note,
11446             item_data.opac_visible
11447         );
11448     END LOOP;
11449
11450     RETURN NULL;
11451 END;
11452 $func$ LANGUAGE PLPGSQL;
11453
11454 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11455 BEGIN
11456     EXECUTE $$
11457         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11458     $$;
11459         RETURN TRUE;
11460 END;
11461 $creator$ LANGUAGE 'plpgsql';
11462
11463 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11464 BEGIN
11465     EXECUTE $$
11466         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11467             audit_id    BIGINT                          PRIMARY KEY,
11468             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11469             audit_action        TEXT                            NOT NULL,
11470             LIKE $$ || sch || $$.$$ || tbl || $$
11471         );
11472     $$;
11473         RETURN TRUE;
11474 END;
11475 $creator$ LANGUAGE 'plpgsql';
11476
11477 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11478 BEGIN
11479     EXECUTE $$
11480         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11481         RETURNS TRIGGER AS $func$
11482         BEGIN
11483             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11484                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11485                     now(),
11486                     SUBSTR(TG_OP,1,1),
11487                     OLD.*;
11488             RETURN NULL;
11489         END;
11490         $func$ LANGUAGE 'plpgsql';
11491     $$;
11492         RETURN TRUE;
11493 END;
11494 $creator$ LANGUAGE 'plpgsql';
11495
11496 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11497 BEGIN
11498     EXECUTE $$
11499         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11500             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11501             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11502     $$;
11503         RETURN TRUE;
11504 END;
11505 $creator$ LANGUAGE 'plpgsql';
11506
11507 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11508 BEGIN
11509     EXECUTE $$
11510         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11511             SELECT      -1, now() as audit_time, '-' as audit_action, *
11512               FROM      $$ || sch || $$.$$ || tbl || $$
11513                 UNION ALL
11514             SELECT      *
11515               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11516     $$;
11517         RETURN TRUE;
11518 END;
11519 $creator$ LANGUAGE 'plpgsql';
11520
11521 -- The main event
11522
11523 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11524 BEGIN
11525     PERFORM acq.create_acq_seq(sch, tbl);
11526     PERFORM acq.create_acq_history(sch, tbl);
11527     PERFORM acq.create_acq_func(sch, tbl);
11528     PERFORM acq.create_acq_update_trigger(sch, tbl);
11529     PERFORM acq.create_acq_lifecycle(sch, tbl);
11530     RETURN TRUE;
11531 END;
11532 $creator$ LANGUAGE 'plpgsql';
11533
11534 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11535
11536 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11537     SELECT  fund.id AS fund,
11538             fund_debit.encumbrance AS encumbrance,
11539             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11540       FROM acq.fund AS fund
11541                         LEFT JOIN acq.fund_debit AS fund_debit
11542                                 ON ( fund.id = fund_debit.fund )
11543       GROUP BY 1,2;
11544
11545 CREATE TABLE acq.debit_attribution (
11546         id                     INT         NOT NULL PRIMARY KEY,
11547         fund_debit             INT         NOT NULL
11548                                            REFERENCES acq.fund_debit
11549                                            DEFERRABLE INITIALLY DEFERRED,
11550     debit_amount           NUMERIC     NOT NULL,
11551         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11552                                            DEFERRABLE INITIALLY DEFERRED,
11553     credit_amount          NUMERIC
11554 );
11555
11556 CREATE INDEX acq_attribution_debit_idx
11557         ON acq.debit_attribution( fund_debit );
11558
11559 CREATE INDEX acq_attribution_credit_idx
11560         ON acq.debit_attribution( funding_source_credit );
11561
11562 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11563 /*
11564 Function to attribute expenditures and encumbrances to funding source credits,
11565 and thereby to funding sources.
11566
11567 Read the debits in chonological order, attributing each one to one or
11568 more funding source credits.  Constraints:
11569
11570 1. Don't attribute more to a credit than the amount of the credit.
11571
11572 2. For a given fund, don't attribute more to a funding source than the
11573 source has allocated to that fund.
11574
11575 3. Attribute debits to credits with deadlines before attributing them to
11576 credits without deadlines.  Otherwise attribute to the earliest credits
11577 first, based on the deadline date when present, or on the effective date
11578 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11579 This ordering is defined by an ORDER BY clause on the view
11580 acq.ordered_funding_source_credit.
11581
11582 Start by truncating the table acq.debit_attribution.  Then insert a row
11583 into that table for each attribution.  If a debit cannot be fully
11584 attributed, insert a row for the unattributable balance, with the 
11585 funding_source_credit and credit_amount columns NULL.
11586 */
11587 DECLARE
11588         curr_fund_source_bal RECORD;
11589         seqno                INT;     -- sequence num for credits applicable to a fund
11590         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11591         fc                   RECORD;  -- used for loading t_fund_credit table
11592         sc                   RECORD;  -- used for loading t_fund_credit table
11593         --
11594         -- Used exclusively in the main loop:
11595         --
11596         deb                 RECORD;   -- current row from acq.fund_debit table
11597         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11598         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11599         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11600         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11601         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11602         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11603         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11604         attrib_count        INT;      -- populates id of acq.debit_attribution
11605 BEGIN
11606         --
11607         -- Load a temporary table.  For each combination of fund and funding source,
11608         -- load an entry with the total amount allocated to that fund by that source.
11609         -- This sum may reflect transfers as well as original allocations.  We will
11610         -- reduce this balance whenever we attribute debits to it.
11611         --
11612         CREATE TEMP TABLE t_fund_source_bal
11613         ON COMMIT DROP AS
11614                 SELECT
11615                         fund AS fund,
11616                         funding_source AS source,
11617                         sum( amount ) AS balance
11618                 FROM
11619                         acq.fund_allocation
11620                 GROUP BY
11621                         fund,
11622                         funding_source
11623                 HAVING
11624                         sum( amount ) > 0;
11625         --
11626         CREATE INDEX t_fund_source_bal_idx
11627                 ON t_fund_source_bal( fund, source );
11628         -------------------------------------------------------------------------------
11629         --
11630         -- Load another temporary table.  For each fund, load zero or more
11631         -- funding source credits from which that fund can get money.
11632         --
11633         CREATE TEMP TABLE t_fund_credit (
11634                 fund        INT,
11635                 seq         INT,
11636                 credit      INT
11637         ) ON COMMIT DROP;
11638         --
11639         FOR fc IN
11640                 SELECT DISTINCT fund
11641                 FROM acq.fund_allocation
11642                 ORDER BY fund
11643         LOOP                  -- Loop over the funds
11644                 seqno := 1;
11645                 FOR sc IN
11646                         SELECT
11647                                 ofsc.id
11648                         FROM
11649                                 acq.ordered_funding_source_credit AS ofsc
11650                         WHERE
11651                                 ofsc.funding_source IN
11652                                 (
11653                                         SELECT funding_source
11654                                         FROM acq.fund_allocation
11655                                         WHERE fund = fc.fund
11656                                 )
11657                 ORDER BY
11658                     ofsc.sort_priority,
11659                     ofsc.sort_date,
11660                     ofsc.id
11661                 LOOP                        -- Add each credit to the list
11662                         INSERT INTO t_fund_credit (
11663                                 fund,
11664                                 seq,
11665                                 credit
11666                         ) VALUES (
11667                                 fc.fund,
11668                                 seqno,
11669                                 sc.id
11670                         );
11671                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11672                         seqno := seqno + 1;
11673                 END LOOP;     -- Loop over credits for a given fund
11674         END LOOP;         -- Loop over funds
11675         --
11676         CREATE INDEX t_fund_credit_idx
11677                 ON t_fund_credit( fund, seq );
11678         -------------------------------------------------------------------------------
11679         --
11680         -- Load yet another temporary table.  This one is a list of funding source
11681         -- credits, with their balances.  We shall reduce those balances as we
11682         -- attribute debits to them.
11683         --
11684         CREATE TEMP TABLE t_credit
11685         ON COMMIT DROP AS
11686         SELECT
11687             fsc.id AS credit,
11688             fsc.funding_source AS source,
11689             fsc.amount AS balance,
11690             fs.currency_type AS currency_type
11691         FROM
11692             acq.funding_source_credit AS fsc,
11693             acq.funding_source fs
11694         WHERE
11695             fsc.funding_source = fs.id
11696                         AND fsc.amount > 0;
11697         --
11698         CREATE INDEX t_credit_idx
11699                 ON t_credit( credit );
11700         --
11701         -------------------------------------------------------------------------------
11702         --
11703         -- Now that we have loaded the lookup tables: loop through the debits,
11704         -- attributing each one to one or more funding source credits.
11705         -- 
11706         truncate table acq.debit_attribution;
11707         --
11708         attrib_count := 0;
11709         FOR deb in
11710                 SELECT
11711                         fd.id,
11712                         fd.fund,
11713                         fd.amount,
11714                         f.currency_type,
11715                         fd.encumbrance
11716                 FROM
11717                         acq.fund_debit fd,
11718                         acq.fund f
11719                 WHERE
11720                         fd.fund = f.id
11721                 ORDER BY
11722                         fd.id
11723         LOOP
11724                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11725                 --
11726                 debit_balance := deb.amount;
11727                 --
11728                 -- Loop over the funding source credits that are eligible
11729                 -- to pay for this debit
11730                 --
11731                 FOR fund_credit IN
11732                         SELECT
11733                                 credit
11734                         FROM
11735                                 t_fund_credit
11736                         WHERE
11737                                 fund = deb.fund
11738                         ORDER BY
11739                                 seq
11740                 LOOP
11741                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11742                         --
11743                         -- Look up the balance for this credit.  If it's zero, then
11744                         -- it's not useful, so treat it as if you didn't find it.
11745                         -- (Actually there shouldn't be any zero balances in the table,
11746                         -- but we check just to make sure.)
11747                         --
11748                         SELECT *
11749                         INTO curr_credit_bal
11750                         FROM t_credit
11751                         WHERE
11752                                 credit = fund_credit.credit
11753                                 AND balance > 0;
11754                         --
11755                         IF curr_credit_bal IS NULL THEN
11756                                 --
11757                                 -- This credit is exhausted; try the next one.
11758                                 --
11759                                 CONTINUE;
11760                         END IF;
11761                         --
11762                         --
11763                         -- At this point we have an applicable credit with some money left.
11764                         -- Now see if the relevant funding_source has any money left.
11765                         --
11766                         -- Look up the balance of the allocation for this combination of
11767                         -- fund and source.  If you find such an entry, but it has a zero
11768                         -- balance, then it's not useful, so treat it as unfound.
11769                         -- (Actually there shouldn't be any zero balances in the table,
11770                         -- but we check just to make sure.)
11771                         --
11772                         SELECT *
11773                         INTO curr_fund_source_bal
11774                         FROM t_fund_source_bal
11775                         WHERE
11776                                 fund = deb.fund
11777                                 AND source = curr_credit_bal.source
11778                                 AND balance > 0;
11779                         --
11780                         IF curr_fund_source_bal IS NULL THEN
11781                                 --
11782                                 -- This fund/source doesn't exist or is already exhausted,
11783                                 -- so we can't use this credit.  Go on to the next one.
11784                                 --
11785                                 CONTINUE;
11786                         END IF;
11787                         --
11788                         -- Convert the available balances to the currency of the fund
11789                         --
11790                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11791                                 curr_credit_bal.currency_type, deb.currency_type );
11792                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11793                                 curr_credit_bal.currency_type, deb.currency_type );
11794                         --
11795                         -- Determine how much we can attribute to this credit: the minimum
11796                         -- of the debit amount, the fund/source balance, and the
11797                         -- credit balance
11798                         --
11799                         --RAISE NOTICE '   deb bal %', debit_balance;
11800                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11801                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11802                         --
11803                         conv_attr_amount := NULL;
11804                         attr_amount := debit_balance;
11805                         --
11806                         IF attr_amount > conv_alloc_balance THEN
11807                                 attr_amount := conv_alloc_balance;
11808                                 conv_attr_amount := curr_fund_source_bal.balance;
11809                         END IF;
11810                         IF attr_amount > conv_cred_balance THEN
11811                                 attr_amount := conv_cred_balance;
11812                                 conv_attr_amount := curr_credit_bal.balance;
11813                         END IF;
11814                         --
11815                         -- If we're attributing all of one of the balances, then that's how
11816                         -- much we will deduct from the balances, and we already captured
11817                         -- that amount above.  Otherwise we must convert the amount of the
11818                         -- attribution from the currency of the fund back to the currency of
11819                         -- the funding source.
11820                         --
11821                         IF conv_attr_amount IS NULL THEN
11822                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11823                                         deb.currency_type, curr_credit_bal.currency_type );
11824                         END IF;
11825                         --
11826                         -- Insert a row to record the attribution
11827                         --
11828                         attrib_count := attrib_count + 1;
11829                         INSERT INTO acq.debit_attribution (
11830                                 id,
11831                                 fund_debit,
11832                                 debit_amount,
11833                                 funding_source_credit,
11834                                 credit_amount
11835                         ) VALUES (
11836                                 attrib_count,
11837                                 deb.id,
11838                                 attr_amount,
11839                                 curr_credit_bal.credit,
11840                                 conv_attr_amount
11841                         );
11842                         --
11843                         -- Subtract the attributed amount from the various balances
11844                         --
11845                         debit_balance := debit_balance - attr_amount;
11846                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11847                         --
11848                         IF curr_fund_source_bal.balance <= 0 THEN
11849                                 --
11850                                 -- This allocation is exhausted.  Delete it so
11851                                 -- that we don't waste time looking at it again.
11852                                 --
11853                                 DELETE FROM t_fund_source_bal
11854                                 WHERE
11855                                         fund = curr_fund_source_bal.fund
11856                                         AND source = curr_fund_source_bal.source;
11857                         ELSE
11858                                 UPDATE t_fund_source_bal
11859                                 SET balance = balance - conv_attr_amount
11860                                 WHERE
11861                                         fund = curr_fund_source_bal.fund
11862                                         AND source = curr_fund_source_bal.source;
11863                         END IF;
11864                         --
11865                         IF curr_credit_bal.balance <= 0 THEN
11866                                 --
11867                                 -- This funding source credit is exhausted.  Delete it
11868                                 -- so that we don't waste time looking at it again.
11869                                 --
11870                                 --DELETE FROM t_credit
11871                                 --WHERE
11872                                 --      credit = curr_credit_bal.credit;
11873                                 --
11874                                 DELETE FROM t_fund_credit
11875                                 WHERE
11876                                         credit = curr_credit_bal.credit;
11877                         ELSE
11878                                 UPDATE t_credit
11879                                 SET balance = curr_credit_bal.balance
11880                                 WHERE
11881                                         credit = curr_credit_bal.credit;
11882                         END IF;
11883                         --
11884                         -- Are we done with this debit yet?
11885                         --
11886                         IF debit_balance <= 0 THEN
11887                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
11888                         END IF;
11889                 END LOOP;       -- End loop over credits
11890                 --
11891                 IF debit_balance <> 0 THEN
11892                         --
11893                         -- We weren't able to attribute this debit, or at least not
11894                         -- all of it.  Insert a row for the unattributed balance.
11895                         --
11896                         attrib_count := attrib_count + 1;
11897                         INSERT INTO acq.debit_attribution (
11898                                 id,
11899                                 fund_debit,
11900                                 debit_amount,
11901                                 funding_source_credit,
11902                                 credit_amount
11903                         ) VALUES (
11904                                 attrib_count,
11905                                 deb.id,
11906                                 debit_balance,
11907                                 NULL,
11908                                 NULL
11909                         );
11910                 END IF;
11911         END LOOP;   -- End of loop over debits
11912 END;
11913 $$ LANGUAGE 'plpgsql';
11914
11915 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
11916 DECLARE
11917     query TEXT;
11918     output TEXT;
11919 BEGIN
11920     query := $q$
11921         SELECT  regexp_replace(
11922                     oils_xpath_string(
11923                         $q$ || quote_literal($3) || $q$,
11924                         marc,
11925                         ' '
11926                     ),
11927                     $q$ || quote_literal($4) || $q$,
11928                     '',
11929                     'g')
11930           FROM  $q$ || $1 || $q$
11931           WHERE id = $q$ || $2;
11932
11933     EXECUTE query INTO output;
11934
11935     -- RAISE NOTICE 'query: %, output; %', query, output;
11936
11937     RETURN output;
11938 END;
11939 $$ LANGUAGE PLPGSQL IMMUTABLE;
11940
11941 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
11942     SELECT extract_marc_field($1,$2,$3,'');
11943 $$ LANGUAGE SQL IMMUTABLE;
11944
11945 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
11946 DECLARE
11947     moved_objects INT := 0;
11948     source_cn     asset.call_number%ROWTYPE;
11949     target_cn     asset.call_number%ROWTYPE;
11950     metarec       metabib.metarecord%ROWTYPE;
11951     hold          action.hold_request%ROWTYPE;
11952     ser_rec       serial.record_entry%ROWTYPE;
11953     uri_count     INT := 0;
11954     counter       INT := 0;
11955     uri_datafield TEXT;
11956     uri_text      TEXT := '';
11957 BEGIN
11958
11959     -- move any 856 entries on records that have at least one MARC-mapped URI entry
11960     SELECT  INTO uri_count COUNT(*)
11961       FROM  asset.uri_call_number_map m
11962             JOIN asset.call_number cn ON (m.call_number = cn.id)
11963       WHERE cn.record = source_record;
11964
11965     IF uri_count > 0 THEN
11966
11967         SELECT  COUNT(*) INTO counter
11968           FROM  oils_xpath_table(
11969                     'id',
11970                     'marc',
11971                     'biblio.record_entry',
11972                     '//*[@tag="856"]',
11973                     'id=' || source_record
11974                 ) as t(i int,c text);
11975
11976         FOR i IN 1 .. counter LOOP
11977             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
11978                         ' tag="856"' || 
11979                         ' ind1="' || FIRST(ind1) || '"'  || 
11980                         ' ind2="' || FIRST(ind2) || '">' || 
11981                         array_to_string(
11982                             array_accum(
11983                                 '<subfield code="' || subfield || '">' ||
11984                                 regexp_replace(
11985                                     regexp_replace(
11986                                         regexp_replace(data,'&','&amp;','g'),
11987                                         '>', '&gt;', 'g'
11988                                     ),
11989                                     '<', '&lt;', 'g'
11990                                 ) || '</subfield>'
11991                             ), ''
11992                         ) || '</datafield>' INTO uri_datafield
11993               FROM  oils_xpath_table(
11994                         'id',
11995                         'marc',
11996                         'biblio.record_entry',
11997                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
11998                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
11999                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
12000                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
12001                         'id=' || source_record
12002                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
12003
12004             uri_text := uri_text || uri_datafield;
12005         END LOOP;
12006
12007         IF uri_text <> '' THEN
12008             UPDATE  biblio.record_entry
12009               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
12010               WHERE id = target_record;
12011         END IF;
12012
12013     END IF;
12014
12015     -- Find and move metarecords to the target record
12016     SELECT  INTO metarec *
12017       FROM  metabib.metarecord
12018       WHERE master_record = source_record;
12019
12020     IF FOUND THEN
12021         UPDATE  metabib.metarecord
12022           SET   master_record = target_record,
12023             mods = NULL
12024           WHERE id = metarec.id;
12025
12026         moved_objects := moved_objects + 1;
12027     END IF;
12028
12029     -- Find call numbers attached to the source ...
12030     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
12031
12032         SELECT  INTO target_cn *
12033           FROM  asset.call_number
12034           WHERE label = source_cn.label
12035             AND owning_lib = source_cn.owning_lib
12036             AND record = target_record;
12037
12038         -- ... and if there's a conflicting one on the target ...
12039         IF FOUND THEN
12040
12041             -- ... move the copies to that, and ...
12042             UPDATE  asset.copy
12043               SET   call_number = target_cn.id
12044               WHERE call_number = source_cn.id;
12045
12046             -- ... move V holds to the move-target call number
12047             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
12048
12049                 UPDATE  action.hold_request
12050                   SET   target = target_cn.id
12051                   WHERE id = hold.id;
12052
12053                 moved_objects := moved_objects + 1;
12054             END LOOP;
12055
12056         -- ... if not ...
12057         ELSE
12058             -- ... just move the call number to the target record
12059             UPDATE  asset.call_number
12060               SET   record = target_record
12061               WHERE id = source_cn.id;
12062         END IF;
12063
12064         moved_objects := moved_objects + 1;
12065     END LOOP;
12066
12067     -- Find T holds targeting the source record ...
12068     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
12069
12070         -- ... and move them to the target record
12071         UPDATE  action.hold_request
12072           SET   target = target_record
12073           WHERE id = hold.id;
12074
12075         moved_objects := moved_objects + 1;
12076     END LOOP;
12077
12078     -- Find serial records targeting the source record ...
12079     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
12080         -- ... and move them to the target record
12081         UPDATE  serial.record_entry
12082           SET   record = target_record
12083           WHERE id = ser_rec.id;
12084
12085         moved_objects := moved_objects + 1;
12086     END LOOP;
12087
12088     -- Finally, "delete" the source record
12089     DELETE FROM biblio.record_entry WHERE id = source_record;
12090
12091     -- That's all, folks!
12092     RETURN moved_objects;
12093 END;
12094 $func$ LANGUAGE plpgsql;
12095
12096 CREATE OR REPLACE FUNCTION acq.transfer_fund(
12097         old_fund   IN INT,
12098         old_amount IN NUMERIC,     -- in currency of old fund
12099         new_fund   IN INT,
12100         new_amount IN NUMERIC,     -- in currency of new fund
12101         user_id    IN INT,
12102         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
12103         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
12104 ) RETURNS VOID AS $$
12105 /* -------------------------------------------------------------------------------
12106
12107 Function to transfer money from one fund to another.
12108
12109 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12110 negative amount for the old (losing) fund and a positive amount for the new
12111 (gaining) fund.  In some cases there may be more than one such pair of entries
12112 in order to pull the money from different funding sources, or more specifically
12113 from different funding source credits.  For each such pair there is also an
12114 entry in acq.fund_transfer.
12115
12116 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12117 choose a funding source for the transferred money to come from.  This choice
12118 must meet two constraints, so far as possible:
12119
12120 1. The amount transferred from a given funding source must not exceed the
12121 amount allocated to the old fund by the funding source.  To that end we
12122 compare the amount being transferred to the amount allocated.
12123
12124 2. We shouldn't transfer money that has already been spent or encumbered, as
12125 defined by the funding attribution process.  We attribute expenses to the
12126 oldest funding source credits first.  In order to avoid transferring that
12127 attributed money, we reverse the priority, transferring from the newest funding
12128 source credits first.  There can be no guarantee that this approach will
12129 avoid overcommitting a fund, but no other approach can do any better.
12130
12131 In this context the age of a funding source credit is defined by the
12132 deadline_date for credits with deadline_dates, and by the effective_date for
12133 credits without deadline_dates, with the proviso that credits with deadline_dates
12134 are all considered "older" than those without.
12135
12136 ----------
12137
12138 In the signature for this function, there is one last parameter commented out,
12139 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12140 driving the main loop has an OR clause commented out, which references the
12141 funding_source_in parameter.
12142
12143 If these lines are uncommented, this function will allow the user optionally to
12144 restrict a fund transfer to a specified funding source.  If the source
12145 parameter is left NULL, then there will be no such restriction.
12146
12147 ------------------------------------------------------------------------------- */ 
12148 DECLARE
12149         same_currency      BOOLEAN;
12150         currency_ratio     NUMERIC;
12151         old_fund_currency  TEXT;
12152         old_remaining      NUMERIC;  -- in currency of old fund
12153         new_fund_currency  TEXT;
12154         new_fund_active    BOOLEAN;
12155         new_remaining      NUMERIC;  -- in currency of new fund
12156         curr_old_amt       NUMERIC;  -- in currency of old fund
12157         curr_new_amt       NUMERIC;  -- in currency of new fund
12158         source_addition    NUMERIC;  -- in currency of funding source
12159         source_deduction   NUMERIC;  -- in currency of funding source
12160         orig_allocated_amt NUMERIC;  -- in currency of funding source
12161         allocated_amt      NUMERIC;  -- in currency of fund
12162         source             RECORD;
12163 BEGIN
12164         --
12165         -- Sanity checks
12166         --
12167         IF old_fund IS NULL THEN
12168                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12169         END IF;
12170         --
12171         IF old_amount IS NULL THEN
12172                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12173         END IF;
12174         --
12175         -- The new fund and its amount must be both NULL or both not NULL.
12176         --
12177         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12178                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12179         END IF;
12180         --
12181         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12182                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12183         END IF;
12184         --
12185         IF user_id IS NULL THEN
12186                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12187         END IF;
12188         --
12189         -- Initialize the amounts to be transferred, each denominated
12190         -- in the currency of its respective fund.  They will be
12191         -- reduced on each iteration of the loop.
12192         --
12193         old_remaining := old_amount;
12194         new_remaining := new_amount;
12195         --
12196         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12197         --      old_amount, old_fund, new_amount, new_fund;
12198         --
12199         -- Get the currency types of the old and new funds.
12200         --
12201         SELECT
12202                 currency_type
12203         INTO
12204                 old_fund_currency
12205         FROM
12206                 acq.fund
12207         WHERE
12208                 id = old_fund;
12209         --
12210         IF old_fund_currency IS NULL THEN
12211                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12212         END IF;
12213         --
12214         IF new_fund IS NOT NULL THEN
12215                 SELECT
12216                         currency_type,
12217                         active
12218                 INTO
12219                         new_fund_currency,
12220                         new_fund_active
12221                 FROM
12222                         acq.fund
12223                 WHERE
12224                         id = new_fund;
12225                 --
12226                 IF new_fund_currency IS NULL THEN
12227                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12228                 ELSIF NOT new_fund_active THEN
12229                         --
12230                         -- No point in putting money into a fund from whence you can't spend it
12231                         --
12232                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12233                 END IF;
12234                 --
12235                 IF new_amount = old_amount THEN
12236                         same_currency := true;
12237                         currency_ratio := 1;
12238                 ELSE
12239                         --
12240                         -- We'll have to translate currency between funds.  We presume that
12241                         -- the calling code has already applied an appropriate exchange rate,
12242                         -- so we'll apply the same conversion to each sub-transfer.
12243                         --
12244                         same_currency := false;
12245                         currency_ratio := new_amount / old_amount;
12246                 END IF;
12247         END IF;
12248         --
12249         -- Identify the funding source(s) from which we want to transfer the money.
12250         -- The principle is that we want to transfer the newest money first, because
12251         -- we spend the oldest money first.  The priority for spending is defined
12252         -- by a sort of the view acq.ordered_funding_source_credit.
12253         --
12254         FOR source in
12255                 SELECT
12256                         ofsc.id,
12257                         ofsc.funding_source,
12258                         ofsc.amount,
12259                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12260                                 AS converted_amt,
12261                         fs.currency_type
12262                 FROM
12263                         acq.ordered_funding_source_credit AS ofsc,
12264                         acq.funding_source fs
12265                 WHERE
12266                         ofsc.funding_source = fs.id
12267                         and ofsc.funding_source IN
12268                         (
12269                                 SELECT funding_source
12270                                 FROM acq.fund_allocation
12271                                 WHERE fund = old_fund
12272                         )
12273                         -- and
12274                         -- (
12275                         --      ofsc.funding_source = funding_source_in
12276                         --      OR funding_source_in IS NULL
12277                         -- )
12278                 ORDER BY
12279                         ofsc.sort_priority desc,
12280                         ofsc.sort_date desc,
12281                         ofsc.id desc
12282         LOOP
12283                 --
12284                 -- Determine how much money the old fund got from this funding source,
12285                 -- denominated in the currency types of the source and of the fund.
12286                 -- This result may reflect transfers from previous iterations.
12287                 --
12288                 SELECT
12289                         COALESCE( sum( amount ), 0 ),
12290                         COALESCE( sum( amount )
12291                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12292                 INTO
12293                         orig_allocated_amt,     -- in currency of the source
12294                         allocated_amt           -- in currency of the old fund
12295                 FROM
12296                         acq.fund_allocation
12297                 WHERE
12298                         fund = old_fund
12299                         and funding_source = source.funding_source;
12300                 --      
12301                 -- Determine how much to transfer from this credit, in the currency
12302                 -- of the fund.   Begin with the amount remaining to be attributed:
12303                 --
12304                 curr_old_amt := old_remaining;
12305                 --
12306                 -- Can't attribute more than was allocated from the fund:
12307                 --
12308                 IF curr_old_amt > allocated_amt THEN
12309                         curr_old_amt := allocated_amt;
12310                 END IF;
12311                 --
12312                 -- Can't attribute more than the amount of the current credit:
12313                 --
12314                 IF curr_old_amt > source.converted_amt THEN
12315                         curr_old_amt := source.converted_amt;
12316                 END IF;
12317                 --
12318                 curr_old_amt := trunc( curr_old_amt, 2 );
12319                 --
12320                 old_remaining := old_remaining - curr_old_amt;
12321                 --
12322                 -- Determine the amount to be deducted, if any,
12323                 -- from the old allocation.
12324                 --
12325                 IF old_remaining > 0 THEN
12326                         --
12327                         -- In this case we're using the whole allocation, so use that
12328                         -- amount directly instead of applying a currency translation
12329                         -- and thereby inviting round-off errors.
12330                         --
12331                         source_deduction := - orig_allocated_amt;
12332                 ELSE 
12333                         source_deduction := trunc(
12334                                 ( - curr_old_amt ) *
12335                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12336                                 2 );
12337                 END IF;
12338                 --
12339                 IF source_deduction <> 0 THEN
12340                         --
12341                         -- Insert negative allocation for old fund in fund_allocation,
12342                         -- converted into the currency of the funding source
12343                         --
12344                         INSERT INTO acq.fund_allocation (
12345                                 funding_source,
12346                                 fund,
12347                                 amount,
12348                                 allocator,
12349                                 note
12350                         ) VALUES (
12351                                 source.funding_source,
12352                                 old_fund,
12353                                 source_deduction,
12354                                 user_id,
12355                                 'Transfer to fund ' || new_fund
12356                         );
12357                 END IF;
12358                 --
12359                 IF new_fund IS NOT NULL THEN
12360                         --
12361                         -- Determine how much to add to the new fund, in
12362                         -- its currency, and how much remains to be added:
12363                         --
12364                         IF same_currency THEN
12365                                 curr_new_amt := curr_old_amt;
12366                         ELSE
12367                                 IF old_remaining = 0 THEN
12368                                         --
12369                                         -- This is the last iteration, so nothing should be left
12370                                         --
12371                                         curr_new_amt := new_remaining;
12372                                         new_remaining := 0;
12373                                 ELSE
12374                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12375                                         new_remaining := new_remaining - curr_new_amt;
12376                                 END IF;
12377                         END IF;
12378                         --
12379                         -- Determine how much to add, if any,
12380                         -- to the new fund's allocation.
12381                         --
12382                         IF old_remaining > 0 THEN
12383                                 --
12384                                 -- In this case we're using the whole allocation, so use that amount
12385                                 -- amount directly instead of applying a currency translation and
12386                                 -- thereby inviting round-off errors.
12387                                 --
12388                                 source_addition := orig_allocated_amt;
12389                         ELSIF source.currency_type = old_fund_currency THEN
12390                                 --
12391                                 -- In this case we don't need a round trip currency translation,
12392                                 -- thereby inviting round-off errors:
12393                                 --
12394                                 source_addition := curr_old_amt;
12395                         ELSE 
12396                                 source_addition := trunc(
12397                                         curr_new_amt *
12398                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12399                                         2 );
12400                         END IF;
12401                         --
12402                         IF source_addition <> 0 THEN
12403                                 --
12404                                 -- Insert positive allocation for new fund in fund_allocation,
12405                                 -- converted to the currency of the founding source
12406                                 --
12407                                 INSERT INTO acq.fund_allocation (
12408                                         funding_source,
12409                                         fund,
12410                                         amount,
12411                                         allocator,
12412                                         note
12413                                 ) VALUES (
12414                                         source.funding_source,
12415                                         new_fund,
12416                                         source_addition,
12417                                         user_id,
12418                                         'Transfer from fund ' || old_fund
12419                                 );
12420                         END IF;
12421                 END IF;
12422                 --
12423                 IF trunc( curr_old_amt, 2 ) <> 0
12424                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12425                         --
12426                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12427                         --
12428                         INSERT INTO acq.fund_transfer (
12429                                 src_fund,
12430                                 src_amount,
12431                                 dest_fund,
12432                                 dest_amount,
12433                                 transfer_user,
12434                                 note,
12435                                 funding_source_credit
12436                         ) VALUES (
12437                                 old_fund,
12438                                 trunc( curr_old_amt, 2 ),
12439                                 new_fund,
12440                                 trunc( curr_new_amt, 2 ),
12441                                 user_id,
12442                                 xfer_note,
12443                                 source.id
12444                         );
12445                 END IF;
12446                 --
12447                 if old_remaining <= 0 THEN
12448                         EXIT;                   -- Nothing more to be transferred
12449                 END IF;
12450         END LOOP;
12451 END;
12452 $$ LANGUAGE plpgsql;
12453
12454 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
12455         old_year INTEGER,
12456         user_id INTEGER,
12457         org_unit_id INTEGER
12458 ) RETURNS VOID AS $$
12459 DECLARE
12460 --
12461 new_id      INT;
12462 old_fund    RECORD;
12463 org_found   BOOLEAN;
12464 --
12465 BEGIN
12466         --
12467         -- Sanity checks
12468         --
12469         IF old_year IS NULL THEN
12470                 RAISE EXCEPTION 'Input year argument is NULL';
12471         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12472                 RAISE EXCEPTION 'Input year is out of range';
12473         END IF;
12474         --
12475         IF user_id IS NULL THEN
12476                 RAISE EXCEPTION 'Input user id argument is NULL';
12477         END IF;
12478         --
12479         IF org_unit_id IS NULL THEN
12480                 RAISE EXCEPTION 'Org unit id argument is NULL';
12481         ELSE
12482                 SELECT TRUE INTO org_found
12483                 FROM actor.org_unit
12484                 WHERE id = org_unit_id;
12485                 --
12486                 IF org_found IS NULL THEN
12487                         RAISE EXCEPTION 'Org unit id is invalid';
12488                 END IF;
12489         END IF;
12490         --
12491         -- Loop over the applicable funds
12492         --
12493         FOR old_fund in SELECT * FROM acq.fund
12494         WHERE
12495                 year = old_year
12496                 AND propagate
12497                 AND org = org_unit_id
12498         LOOP
12499                 BEGIN
12500                         INSERT INTO acq.fund (
12501                                 org,
12502                                 name,
12503                                 year,
12504                                 currency_type,
12505                                 code,
12506                                 rollover,
12507                                 propagate,
12508                                 balance_warning_percent,
12509                                 balance_stop_percent
12510                         ) VALUES (
12511                                 old_fund.org,
12512                                 old_fund.name,
12513                                 old_year + 1,
12514                                 old_fund.currency_type,
12515                                 old_fund.code,
12516                                 old_fund.rollover,
12517                                 true,
12518                                 old_fund.balance_warning_percent,
12519                                 old_fund.balance_stop_percent
12520                         )
12521                         RETURNING id INTO new_id;
12522                 EXCEPTION
12523                         WHEN unique_violation THEN
12524                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12525                                 CONTINUE;
12526                 END;
12527                 --RAISE NOTICE 'Propagating fund % to fund %',
12528                 --      old_fund.code, new_id;
12529         END LOOP;
12530 END;
12531 $$ LANGUAGE plpgsql;
12532
12533 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12534         old_year INTEGER,
12535         user_id INTEGER,
12536         org_unit_id INTEGER
12537 ) RETURNS VOID AS $$
12538 DECLARE
12539 --
12540 new_id      INT;
12541 old_fund    RECORD;
12542 org_found   BOOLEAN;
12543 --
12544 BEGIN
12545         --
12546         -- Sanity checks
12547         --
12548         IF old_year IS NULL THEN
12549                 RAISE EXCEPTION 'Input year argument is NULL';
12550         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12551                 RAISE EXCEPTION 'Input year is out of range';
12552         END IF;
12553         --
12554         IF user_id IS NULL THEN
12555                 RAISE EXCEPTION 'Input user id argument is NULL';
12556         END IF;
12557         --
12558         IF org_unit_id IS NULL THEN
12559                 RAISE EXCEPTION 'Org unit id argument is NULL';
12560         ELSE
12561                 SELECT TRUE INTO org_found
12562                 FROM actor.org_unit
12563                 WHERE id = org_unit_id;
12564                 --
12565                 IF org_found IS NULL THEN
12566                         RAISE EXCEPTION 'Org unit id is invalid';
12567                 END IF;
12568         END IF;
12569         --
12570         -- Loop over the applicable funds
12571         --
12572         FOR old_fund in SELECT * FROM acq.fund
12573         WHERE
12574                 year = old_year
12575                 AND propagate
12576                 AND org in (
12577                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12578                 )
12579         LOOP
12580                 BEGIN
12581                         INSERT INTO acq.fund (
12582                                 org,
12583                                 name,
12584                                 year,
12585                                 currency_type,
12586                                 code,
12587                                 rollover,
12588                                 propagate,
12589                                 balance_warning_percent,
12590                                 balance_stop_percent
12591                         ) VALUES (
12592                                 old_fund.org,
12593                                 old_fund.name,
12594                                 old_year + 1,
12595                                 old_fund.currency_type,
12596                                 old_fund.code,
12597                                 old_fund.rollover,
12598                                 true,
12599                                 old_fund.balance_warning_percent,
12600                                 old_fund.balance_stop_percent
12601                         )
12602                         RETURNING id INTO new_id;
12603                 EXCEPTION
12604                         WHEN unique_violation THEN
12605                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12606                                 CONTINUE;
12607                 END;
12608                 --RAISE NOTICE 'Propagating fund % to fund %',
12609                 --      old_fund.code, new_id;
12610         END LOOP;
12611 END;
12612 $$ LANGUAGE plpgsql;
12613
12614 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12615         old_year INTEGER,
12616         user_id INTEGER,
12617         org_unit_id INTEGER
12618 ) RETURNS VOID AS $$
12619 DECLARE
12620 --
12621 new_fund    INT;
12622 new_year    INT := old_year + 1;
12623 org_found   BOOL;
12624 xfer_amount NUMERIC;
12625 roll_fund   RECORD;
12626 deb         RECORD;
12627 detail      RECORD;
12628 --
12629 BEGIN
12630         --
12631         -- Sanity checks
12632         --
12633         IF old_year IS NULL THEN
12634                 RAISE EXCEPTION 'Input year argument is NULL';
12635     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12636         RAISE EXCEPTION 'Input year is out of range';
12637         END IF;
12638         --
12639         IF user_id IS NULL THEN
12640                 RAISE EXCEPTION 'Input user id argument is NULL';
12641         END IF;
12642         --
12643         IF org_unit_id IS NULL THEN
12644                 RAISE EXCEPTION 'Org unit id argument is NULL';
12645         ELSE
12646                 --
12647                 -- Validate the org unit
12648                 --
12649                 SELECT TRUE
12650                 INTO org_found
12651                 FROM actor.org_unit
12652                 WHERE id = org_unit_id;
12653                 --
12654                 IF org_found IS NULL THEN
12655                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12656                 END IF;
12657         END IF;
12658         --
12659         -- Loop over the propagable funds to identify the details
12660         -- from the old fund plus the id of the new one, if it exists.
12661         --
12662         FOR roll_fund in
12663         SELECT
12664             oldf.id AS old_fund,
12665             oldf.org,
12666             oldf.name,
12667             oldf.currency_type,
12668             oldf.code,
12669                 oldf.rollover,
12670             newf.id AS new_fund_id
12671         FROM
12672         acq.fund AS oldf
12673         LEFT JOIN acq.fund AS newf
12674                 ON ( oldf.code = newf.code )
12675         WHERE
12676                     oldf.org = org_unit_id
12677                 and oldf.year = old_year
12678                 and oldf.propagate
12679         and newf.year = new_year
12680         LOOP
12681                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12682                 --
12683                 IF roll_fund.new_fund_id IS NULL THEN
12684                         --
12685                         -- The old fund hasn't been propagated yet.  Propagate it now.
12686                         --
12687                         INSERT INTO acq.fund (
12688                                 org,
12689                                 name,
12690                                 year,
12691                                 currency_type,
12692                                 code,
12693                                 rollover,
12694                                 propagate,
12695                                 balance_warning_percent,
12696                                 balance_stop_percent
12697                         ) VALUES (
12698                                 roll_fund.org,
12699                                 roll_fund.name,
12700                                 new_year,
12701                                 roll_fund.currency_type,
12702                                 roll_fund.code,
12703                                 true,
12704                                 true,
12705                                 roll_fund.balance_warning_percent,
12706                                 roll_fund.balance_stop_percent
12707                         )
12708                         RETURNING id INTO new_fund;
12709                 ELSE
12710                         new_fund = roll_fund.new_fund_id;
12711                 END IF;
12712                 --
12713                 -- Determine the amount to transfer
12714                 --
12715                 SELECT amount
12716                 INTO xfer_amount
12717                 FROM acq.fund_spent_balance
12718                 WHERE fund = roll_fund.old_fund;
12719                 --
12720                 IF xfer_amount <> 0 THEN
12721                         IF roll_fund.rollover THEN
12722                                 --
12723                                 -- Transfer balance from old fund to new
12724                                 --
12725                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12726                                 --
12727                                 PERFORM acq.transfer_fund(
12728                                         roll_fund.old_fund,
12729                                         xfer_amount,
12730                                         new_fund,
12731                                         xfer_amount,
12732                                         user_id,
12733                                         'Rollover'
12734                                 );
12735                         ELSE
12736                                 --
12737                                 -- Transfer balance from old fund to the void
12738                                 --
12739                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12740                                 --
12741                                 PERFORM acq.transfer_fund(
12742                                         roll_fund.old_fund,
12743                                         xfer_amount,
12744                                         NULL,
12745                                         NULL,
12746                                         user_id,
12747                                         'Rollover'
12748                                 );
12749                         END IF;
12750                 END IF;
12751                 --
12752                 IF roll_fund.rollover THEN
12753                         --
12754                         -- Move any lineitems from the old fund to the new one
12755                         -- where the associated debit is an encumbrance.
12756                         --
12757                         -- Any other tables tying expenditure details to funds should
12758                         -- receive similar treatment.  At this writing there are none.
12759                         --
12760                         UPDATE acq.lineitem_detail
12761                         SET fund = new_fund
12762                         WHERE
12763                         fund = roll_fund.old_fund -- this condition may be redundant
12764                         AND fund_debit in
12765                         (
12766                                 SELECT id
12767                                 FROM acq.fund_debit
12768                                 WHERE
12769                                 fund = roll_fund.old_fund
12770                                 AND encumbrance
12771                         );
12772                         --
12773                         -- Move encumbrance debits from the old fund to the new fund
12774                         --
12775                         UPDATE acq.fund_debit
12776                         SET fund = new_fund
12777                         wHERE
12778                                 fund = roll_fund.old_fund
12779                                 AND encumbrance;
12780                 END IF;
12781                 --
12782                 -- Mark old fund as inactive, now that we've closed it
12783                 --
12784                 UPDATE acq.fund
12785                 SET active = FALSE
12786                 WHERE id = roll_fund.old_fund;
12787         END LOOP;
12788 END;
12789 $$ LANGUAGE plpgsql;
12790
12791 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12792         old_year INTEGER,
12793         user_id INTEGER,
12794         org_unit_id INTEGER
12795 ) RETURNS VOID AS $$
12796 DECLARE
12797 --
12798 new_fund    INT;
12799 new_year    INT := old_year + 1;
12800 org_found   BOOL;
12801 xfer_amount NUMERIC;
12802 roll_fund   RECORD;
12803 deb         RECORD;
12804 detail      RECORD;
12805 --
12806 BEGIN
12807         --
12808         -- Sanity checks
12809         --
12810         IF old_year IS NULL THEN
12811                 RAISE EXCEPTION 'Input year argument is NULL';
12812     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12813         RAISE EXCEPTION 'Input year is out of range';
12814         END IF;
12815         --
12816         IF user_id IS NULL THEN
12817                 RAISE EXCEPTION 'Input user id argument is NULL';
12818         END IF;
12819         --
12820         IF org_unit_id IS NULL THEN
12821                 RAISE EXCEPTION 'Org unit id argument is NULL';
12822         ELSE
12823                 --
12824                 -- Validate the org unit
12825                 --
12826                 SELECT TRUE
12827                 INTO org_found
12828                 FROM actor.org_unit
12829                 WHERE id = org_unit_id;
12830                 --
12831                 IF org_found IS NULL THEN
12832                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12833                 END IF;
12834         END IF;
12835         --
12836         -- Loop over the propagable funds to identify the details
12837         -- from the old fund plus the id of the new one, if it exists.
12838         --
12839         FOR roll_fund in
12840         SELECT
12841             oldf.id AS old_fund,
12842             oldf.org,
12843             oldf.name,
12844             oldf.currency_type,
12845             oldf.code,
12846                 oldf.rollover,
12847             newf.id AS new_fund_id
12848         FROM
12849         acq.fund AS oldf
12850         LEFT JOIN acq.fund AS newf
12851                 ON ( oldf.code = newf.code )
12852         WHERE
12853                     oldf.year = old_year
12854                 AND oldf.propagate
12855         AND newf.year = new_year
12856                 AND oldf.org in (
12857                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12858                 )
12859         LOOP
12860                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12861                 --
12862                 IF roll_fund.new_fund_id IS NULL THEN
12863                         --
12864                         -- The old fund hasn't been propagated yet.  Propagate it now.
12865                         --
12866                         INSERT INTO acq.fund (
12867                                 org,
12868                                 name,
12869                                 year,
12870                                 currency_type,
12871                                 code,
12872                                 rollover,
12873                                 propagate,
12874                                 balance_warning_percent,
12875                                 balance_stop_percent
12876                         ) VALUES (
12877                                 roll_fund.org,
12878                                 roll_fund.name,
12879                                 new_year,
12880                                 roll_fund.currency_type,
12881                                 roll_fund.code,
12882                                 true,
12883                                 true,
12884                                 roll_fund.balance_warning_percent,
12885                                 roll_fund.balance_stop_percent
12886                         )
12887                         RETURNING id INTO new_fund;
12888                 ELSE
12889                         new_fund = roll_fund.new_fund_id;
12890                 END IF;
12891                 --
12892                 -- Determine the amount to transfer
12893                 --
12894                 SELECT amount
12895                 INTO xfer_amount
12896                 FROM acq.fund_spent_balance
12897                 WHERE fund = roll_fund.old_fund;
12898                 --
12899                 IF xfer_amount <> 0 THEN
12900                         IF roll_fund.rollover THEN
12901                                 --
12902                                 -- Transfer balance from old fund to new
12903                                 --
12904                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12905                                 --
12906                                 PERFORM acq.transfer_fund(
12907                                         roll_fund.old_fund,
12908                                         xfer_amount,
12909                                         new_fund,
12910                                         xfer_amount,
12911                                         user_id,
12912                                         'Rollover'
12913                                 );
12914                         ELSE
12915                                 --
12916                                 -- Transfer balance from old fund to the void
12917                                 --
12918                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12919                                 --
12920                                 PERFORM acq.transfer_fund(
12921                                         roll_fund.old_fund,
12922                                         xfer_amount,
12923                                         NULL,
12924                                         NULL,
12925                                         user_id,
12926                                         'Rollover'
12927                                 );
12928                         END IF;
12929                 END IF;
12930                 --
12931                 IF roll_fund.rollover THEN
12932                         --
12933                         -- Move any lineitems from the old fund to the new one
12934                         -- where the associated debit is an encumbrance.
12935                         --
12936                         -- Any other tables tying expenditure details to funds should
12937                         -- receive similar treatment.  At this writing there are none.
12938                         --
12939                         UPDATE acq.lineitem_detail
12940                         SET fund = new_fund
12941                         WHERE
12942                         fund = roll_fund.old_fund -- this condition may be redundant
12943                         AND fund_debit in
12944                         (
12945                                 SELECT id
12946                                 FROM acq.fund_debit
12947                                 WHERE
12948                                 fund = roll_fund.old_fund
12949                                 AND encumbrance
12950                         );
12951                         --
12952                         -- Move encumbrance debits from the old fund to the new fund
12953                         --
12954                         UPDATE acq.fund_debit
12955                         SET fund = new_fund
12956                         wHERE
12957                                 fund = roll_fund.old_fund
12958                                 AND encumbrance;
12959                 END IF;
12960                 --
12961                 -- Mark old fund as inactive, now that we've closed it
12962                 --
12963                 UPDATE acq.fund
12964                 SET active = FALSE
12965                 WHERE id = roll_fund.old_fund;
12966         END LOOP;
12967 END;
12968 $$ LANGUAGE plpgsql;
12969
12970 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
12971     SELECT regexp_replace($1, ',', '', 'g');
12972 $$ LANGUAGE SQL STRICT IMMUTABLE;
12973
12974 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
12975     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
12976 $$ LANGUAGE SQL STRICT IMMUTABLE;
12977
12978 CREATE TABLE acq.distribution_formula_application (
12979     id BIGSERIAL PRIMARY KEY,
12980     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
12981     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
12982     formula INT NOT NULL
12983         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
12984     lineitem INT NOT NULL
12985         REFERENCES acq.lineitem( id )
12986                 ON DELETE CASCADE
12987                 DEFERRABLE INITIALLY DEFERRED
12988 );
12989
12990 CREATE INDEX acqdfa_df_idx
12991     ON acq.distribution_formula_application(formula);
12992 CREATE INDEX acqdfa_li_idx
12993     ON acq.distribution_formula_application(lineitem);
12994 CREATE INDEX acqdfa_creator_idx
12995     ON acq.distribution_formula_application(creator);
12996
12997 CREATE TABLE acq.user_request_type (
12998     id      SERIAL  PRIMARY KEY,
12999     label   TEXT    NOT NULL UNIQUE -- i18n-ize
13000 );
13001
13002 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
13003 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
13004 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
13005 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
13006 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
13007
13008 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
13009
13010 CREATE TABLE acq.cancel_reason (
13011         id            SERIAL            PRIMARY KEY,
13012         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
13013                                         DEFERRABLE INITIALLY DEFERRED,
13014         label         TEXT              NOT NULL,
13015         description   TEXT              NOT NULL,
13016         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
13017         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
13018 );
13019
13020 -- Reserve ids 1-999 for stock reasons
13021 -- Reserve ids 1000-1999 for EDI reasons
13022 -- 2000+ are available for staff to create
13023
13024 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
13025
13026 CREATE TABLE acq.user_request (
13027     id                  SERIAL  PRIMARY KEY,
13028     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
13029     hold                BOOL    NOT NULL DEFAULT TRUE,
13030
13031     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
13032     holdable_formats    TEXT,           -- nullable, for use in hold creation
13033     phone_notify        TEXT,
13034     email_notify        BOOL    NOT NULL DEFAULT TRUE,
13035     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
13036     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
13037     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
13038     need_before         TIMESTAMPTZ,    -- don't create holds after this
13039     max_fee             TEXT,
13040
13041     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
13042     isxn                TEXT,
13043     title               TEXT,
13044     volume              TEXT,
13045     author              TEXT,
13046     article_title       TEXT,
13047     article_pages       TEXT,
13048     publisher           TEXT,
13049     location            TEXT,
13050     pubdate             TEXT,
13051     mentioned           TEXT,
13052     other_info          TEXT,
13053         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
13054                                              DEFERRABLE INITIALLY DEFERRED
13055 );
13056
13057 CREATE TABLE acq.lineitem_alert_text (
13058         id               SERIAL         PRIMARY KEY,
13059         code             TEXT           NOT NULL,
13060         description      TEXT,
13061         owning_lib       INT            NOT NULL
13062                                         REFERENCES actor.org_unit(id)
13063                                         DEFERRABLE INITIALLY DEFERRED,
13064         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
13065 );
13066
13067 ALTER TABLE acq.lineitem_note
13068         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
13069                                          DEFERRABLE INITIALLY DEFERRED;
13070
13071 -- add ON DELETE CASCADE clause
13072
13073 ALTER TABLE acq.lineitem_note
13074         DROP CONSTRAINT lineitem_note_lineitem_fkey;
13075
13076 ALTER TABLE acq.lineitem_note
13077         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13078                 ON DELETE CASCADE
13079                 DEFERRABLE INITIALLY DEFERRED;
13080
13081 ALTER TABLE acq.lineitem_note
13082         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13083
13084 CREATE TABLE acq.invoice_method (
13085     code    TEXT    PRIMARY KEY,
13086     name    TEXT    NOT NULL -- i18n-ize
13087 );
13088 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
13089 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
13090
13091 CREATE TABLE acq.invoice_payment_method (
13092         code      TEXT     PRIMARY KEY,
13093         name      TEXT     NOT NULL
13094 );
13095
13096 CREATE TABLE acq.invoice (
13097     id             SERIAL      PRIMARY KEY,
13098     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
13099     provider       INT         NOT NULL REFERENCES acq.provider (id),
13100     shipper        INT         NOT NULL REFERENCES acq.provider (id),
13101     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13102     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
13103     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
13104     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
13105         payment_auth   TEXT,
13106         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
13107                                    DEFERRABLE INITIALLY DEFERRED,
13108         note           TEXT,
13109     complete       BOOL        NOT NULL DEFAULT FALSE,
13110     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13111 );
13112
13113 CREATE TABLE acq.invoice_entry (
13114     id              SERIAL      PRIMARY KEY,
13115     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13116     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13117     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13118     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13119     phys_item_count INT, -- and how many did staff count
13120     note            TEXT,
13121     billed_per_item BOOL,
13122     cost_billed     NUMERIC(8,2),
13123     actual_cost     NUMERIC(8,2),
13124         amount_paid     NUMERIC (8,2)
13125 );
13126
13127 CREATE TABLE acq.invoice_item_type (
13128     code    TEXT    PRIMARY KEY,
13129     name    TEXT    NOT NULL, -- i18n-ize
13130         prorate BOOL    NOT NULL DEFAULT FALSE
13131 );
13132
13133 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13134 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13135 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13136 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13137 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13138 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13139
13140 CREATE TABLE acq.po_item (
13141         id              SERIAL      PRIMARY KEY,
13142         purchase_order  INT         REFERENCES acq.purchase_order (id)
13143                                     ON UPDATE CASCADE ON DELETE SET NULL
13144                                     DEFERRABLE INITIALLY DEFERRED,
13145         fund_debit      INT         REFERENCES acq.fund_debit (id)
13146                                     DEFERRABLE INITIALLY DEFERRED,
13147         inv_item_type   TEXT        NOT NULL
13148                                     REFERENCES acq.invoice_item_type (code)
13149                                     DEFERRABLE INITIALLY DEFERRED,
13150         title           TEXT,
13151         author          TEXT,
13152         note            TEXT,
13153         estimated_cost  NUMERIC(8,2),
13154         fund            INT         REFERENCES acq.fund (id)
13155                                     DEFERRABLE INITIALLY DEFERRED,
13156         target          BIGINT
13157 );
13158
13159 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13160     id              SERIAL      PRIMARY KEY,
13161     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13162     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13163     fund_debit      INT         REFERENCES acq.fund_debit (id),
13164     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13165     title           TEXT,
13166     author          TEXT,
13167     note            TEXT,
13168     cost_billed     NUMERIC(8,2),
13169     actual_cost     NUMERIC(8,2),
13170     fund            INT         REFERENCES acq.fund (id)
13171                                 DEFERRABLE INITIALLY DEFERRED,
13172     amount_paid     NUMERIC (8,2),
13173     po_item         INT         REFERENCES acq.po_item (id)
13174                                 DEFERRABLE INITIALLY DEFERRED,
13175     target          BIGINT
13176 );
13177
13178 CREATE TABLE acq.edi_message (
13179     id               SERIAL          PRIMARY KEY,
13180     account          INTEGER         REFERENCES acq.edi_account(id)
13181                                      DEFERRABLE INITIALLY DEFERRED,
13182     remote_file      TEXT,
13183     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13184     translate_time   TIMESTAMPTZ,
13185     process_time     TIMESTAMPTZ,
13186     error_time       TIMESTAMPTZ,
13187     status           TEXT            NOT NULL DEFAULT 'new'
13188                                      CONSTRAINT status_value CHECK
13189                                      ( status IN (
13190                                         'new',          -- needs to be translated
13191                                         'translated',   -- needs to be processed
13192                                         'trans_error',  -- error in translation step
13193                                         'processed',    -- needs to have remote_file deleted
13194                                         'proc_error',   -- error in processing step
13195                                         'delete_error', -- error in deletion
13196                                         'retry',        -- need to retry
13197                                         'complete'      -- done
13198                                      )),
13199     edi              TEXT,
13200     jedi             TEXT,
13201     error            TEXT,
13202     purchase_order   INT             REFERENCES acq.purchase_order
13203                                      DEFERRABLE INITIALLY DEFERRED,
13204     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13205                                      CHECK ( message_type IN (
13206                                         'ORDERS',
13207                                         'ORDRSP',
13208                                         'INVOIC',
13209                                         'OSTENQ',
13210                                         'OSTRPT'
13211                                      ))
13212 );
13213
13214 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13215
13216 ALTER TABLE acq.provider_address
13217         ADD COLUMN fax_phone TEXT;
13218
13219 ALTER TABLE acq.provider_contact_address
13220         ADD COLUMN fax_phone TEXT;
13221
13222 CREATE TABLE acq.provider_note (
13223     id      SERIAL              PRIMARY KEY,
13224     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13225     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13226     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13227     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13228     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13229     value       TEXT            NOT NULL
13230 );
13231 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13232 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13233 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13234
13235 -- For each fund: the total allocation from all sources, in the
13236 -- currency of the fund (or 0 if there are no allocations)
13237
13238 CREATE VIEW acq.all_fund_allocation_total AS
13239 SELECT
13240     f.id AS fund,
13241     COALESCE( SUM( a.amount * acq.exchange_ratio(
13242         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13243     AS amount
13244 FROM
13245     acq.fund f
13246         LEFT JOIN acq.fund_allocation a
13247             ON a.fund = f.id
13248         LEFT JOIN acq.funding_source s
13249             ON a.funding_source = s.id
13250 GROUP BY
13251     f.id;
13252
13253 -- For every fund: the total encumbrances (or 0 if none),
13254 -- in the currency of the fund.
13255
13256 CREATE VIEW acq.all_fund_encumbrance_total AS
13257 SELECT
13258         f.id AS fund,
13259         COALESCE( encumb.amount, 0 ) AS amount
13260 FROM
13261         acq.fund AS f
13262                 LEFT JOIN (
13263                         SELECT
13264                                 fund,
13265                                 sum( amount ) AS amount
13266                         FROM
13267                                 acq.fund_debit
13268                         WHERE
13269                                 encumbrance
13270                         GROUP BY fund
13271                 ) AS encumb
13272                         ON f.id = encumb.fund;
13273
13274 -- For every fund: the total spent (or 0 if none),
13275 -- in the currency of the fund.
13276
13277 CREATE VIEW acq.all_fund_spent_total AS
13278 SELECT
13279     f.id AS fund,
13280     COALESCE( spent.amount, 0 ) AS amount
13281 FROM
13282     acq.fund AS f
13283         LEFT JOIN (
13284             SELECT
13285                 fund,
13286                 sum( amount ) AS amount
13287             FROM
13288                 acq.fund_debit
13289             WHERE
13290                 NOT encumbrance
13291             GROUP BY fund
13292         ) AS spent
13293             ON f.id = spent.fund;
13294
13295 -- For each fund: the amount not yet spent, in the currency
13296 -- of the fund.  May include encumbrances.
13297
13298 CREATE VIEW acq.all_fund_spent_balance AS
13299 SELECT
13300         c.fund,
13301         c.amount - d.amount AS amount
13302 FROM acq.all_fund_allocation_total c
13303     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13304
13305 -- For each fund: the amount neither spent nor encumbered,
13306 -- in the currency of the fund
13307
13308 CREATE VIEW acq.all_fund_combined_balance AS
13309 SELECT
13310      a.fund,
13311      a.amount - COALESCE( c.amount, 0 ) AS amount
13312 FROM
13313      acq.all_fund_allocation_total a
13314         LEFT OUTER JOIN (
13315             SELECT
13316                 fund,
13317                 SUM( amount ) AS amount
13318             FROM
13319                 acq.fund_debit
13320             GROUP BY
13321                 fund
13322         ) AS c USING ( fund );
13323
13324 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 $$
13325 DECLARE
13326         suffix TEXT;
13327         bucket_row RECORD;
13328         picklist_row RECORD;
13329         queue_row RECORD;
13330         folder_row RECORD;
13331 BEGIN
13332
13333     -- do some initial cleanup 
13334     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13335     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13336     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13337
13338     -- actor.*
13339     IF del_cards THEN
13340         DELETE FROM actor.card where usr = src_usr;
13341     ELSE
13342         IF deactivate_cards THEN
13343             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13344         END IF;
13345         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13346     END IF;
13347
13348
13349     IF del_addrs THEN
13350         DELETE FROM actor.usr_address WHERE usr = src_usr;
13351     ELSE
13352         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13353     END IF;
13354
13355     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13356     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13357     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13358     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13359     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13360
13361     -- permission.*
13362     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13363     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13364     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13365     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13366
13367
13368     -- container.*
13369         
13370         -- For each *_bucket table: transfer every bucket belonging to src_usr
13371         -- into the custody of dest_usr.
13372         --
13373         -- In order to avoid colliding with an existing bucket owned by
13374         -- the destination user, append the source user's id (in parenthesese)
13375         -- to the name.  If you still get a collision, add successive
13376         -- spaces to the name and keep trying until you succeed.
13377         --
13378         FOR bucket_row in
13379                 SELECT id, name
13380                 FROM   container.biblio_record_entry_bucket
13381                 WHERE  owner = src_usr
13382         LOOP
13383                 suffix := ' (' || src_usr || ')';
13384                 LOOP
13385                         BEGIN
13386                                 UPDATE  container.biblio_record_entry_bucket
13387                                 SET     owner = dest_usr, name = name || suffix
13388                                 WHERE   id = bucket_row.id;
13389                         EXCEPTION WHEN unique_violation THEN
13390                                 suffix := suffix || ' ';
13391                                 CONTINUE;
13392                         END;
13393                         EXIT;
13394                 END LOOP;
13395         END LOOP;
13396
13397         FOR bucket_row in
13398                 SELECT id, name
13399                 FROM   container.call_number_bucket
13400                 WHERE  owner = src_usr
13401         LOOP
13402                 suffix := ' (' || src_usr || ')';
13403                 LOOP
13404                         BEGIN
13405                                 UPDATE  container.call_number_bucket
13406                                 SET     owner = dest_usr, name = name || suffix
13407                                 WHERE   id = bucket_row.id;
13408                         EXCEPTION WHEN unique_violation THEN
13409                                 suffix := suffix || ' ';
13410                                 CONTINUE;
13411                         END;
13412                         EXIT;
13413                 END LOOP;
13414         END LOOP;
13415
13416         FOR bucket_row in
13417                 SELECT id, name
13418                 FROM   container.copy_bucket
13419                 WHERE  owner = src_usr
13420         LOOP
13421                 suffix := ' (' || src_usr || ')';
13422                 LOOP
13423                         BEGIN
13424                                 UPDATE  container.copy_bucket
13425                                 SET     owner = dest_usr, name = name || suffix
13426                                 WHERE   id = bucket_row.id;
13427                         EXCEPTION WHEN unique_violation THEN
13428                                 suffix := suffix || ' ';
13429                                 CONTINUE;
13430                         END;
13431                         EXIT;
13432                 END LOOP;
13433         END LOOP;
13434
13435         FOR bucket_row in
13436                 SELECT id, name
13437                 FROM   container.user_bucket
13438                 WHERE  owner = src_usr
13439         LOOP
13440                 suffix := ' (' || src_usr || ')';
13441                 LOOP
13442                         BEGIN
13443                                 UPDATE  container.user_bucket
13444                                 SET     owner = dest_usr, name = name || suffix
13445                                 WHERE   id = bucket_row.id;
13446                         EXCEPTION WHEN unique_violation THEN
13447                                 suffix := suffix || ' ';
13448                                 CONTINUE;
13449                         END;
13450                         EXIT;
13451                 END LOOP;
13452         END LOOP;
13453
13454         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13455
13456     -- vandelay.*
13457         -- transfer queues the same way we transfer buckets (see above)
13458         FOR queue_row in
13459                 SELECT id, name
13460                 FROM   vandelay.queue
13461                 WHERE  owner = src_usr
13462         LOOP
13463                 suffix := ' (' || src_usr || ')';
13464                 LOOP
13465                         BEGIN
13466                                 UPDATE  vandelay.queue
13467                                 SET     owner = dest_usr, name = name || suffix
13468                                 WHERE   id = queue_row.id;
13469                         EXCEPTION WHEN unique_violation THEN
13470                                 suffix := suffix || ' ';
13471                                 CONTINUE;
13472                         END;
13473                         EXIT;
13474                 END LOOP;
13475         END LOOP;
13476
13477     -- money.*
13478     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13479     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13480     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13481     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13482     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13483
13484     -- action.*
13485     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13486     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13487     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13488
13489     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13490     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13491     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13492     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13493
13494     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13495     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13496     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13497     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13498     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13499
13500     -- acq.*
13501     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13502         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13503
13504         -- transfer picklists the same way we transfer buckets (see above)
13505         FOR picklist_row in
13506                 SELECT id, name
13507                 FROM   acq.picklist
13508                 WHERE  owner = src_usr
13509         LOOP
13510                 suffix := ' (' || src_usr || ')';
13511                 LOOP
13512                         BEGIN
13513                                 UPDATE  acq.picklist
13514                                 SET     owner = dest_usr, name = name || suffix
13515                                 WHERE   id = picklist_row.id;
13516                         EXCEPTION WHEN unique_violation THEN
13517                                 suffix := suffix || ' ';
13518                                 CONTINUE;
13519                         END;
13520                         EXIT;
13521                 END LOOP;
13522         END LOOP;
13523
13524     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13525     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13526     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13527     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13528     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13529     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13530     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13531     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13532
13533     -- asset.*
13534     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13535     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13536     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13537     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13538     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13539     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13540
13541     -- serial.*
13542     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13543     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13544
13545     -- reporter.*
13546     -- It's not uncommon to define the reporter schema in a replica 
13547     -- DB only, so don't assume these tables exist in the write DB.
13548     BEGIN
13549         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13550     EXCEPTION WHEN undefined_table THEN
13551         -- do nothing
13552     END;
13553     BEGIN
13554         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13555     EXCEPTION WHEN undefined_table THEN
13556         -- do nothing
13557     END;
13558     BEGIN
13559         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13560     EXCEPTION WHEN undefined_table THEN
13561         -- do nothing
13562     END;
13563     BEGIN
13564                 -- transfer folders the same way we transfer buckets (see above)
13565                 FOR folder_row in
13566                         SELECT id, name
13567                         FROM   reporter.template_folder
13568                         WHERE  owner = src_usr
13569                 LOOP
13570                         suffix := ' (' || src_usr || ')';
13571                         LOOP
13572                                 BEGIN
13573                                         UPDATE  reporter.template_folder
13574                                         SET     owner = dest_usr, name = name || suffix
13575                                         WHERE   id = folder_row.id;
13576                                 EXCEPTION WHEN unique_violation THEN
13577                                         suffix := suffix || ' ';
13578                                         CONTINUE;
13579                                 END;
13580                                 EXIT;
13581                         END LOOP;
13582                 END LOOP;
13583     EXCEPTION WHEN undefined_table THEN
13584         -- do nothing
13585     END;
13586     BEGIN
13587                 -- transfer folders the same way we transfer buckets (see above)
13588                 FOR folder_row in
13589                         SELECT id, name
13590                         FROM   reporter.report_folder
13591                         WHERE  owner = src_usr
13592                 LOOP
13593                         suffix := ' (' || src_usr || ')';
13594                         LOOP
13595                                 BEGIN
13596                                         UPDATE  reporter.report_folder
13597                                         SET     owner = dest_usr, name = name || suffix
13598                                         WHERE   id = folder_row.id;
13599                                 EXCEPTION WHEN unique_violation THEN
13600                                         suffix := suffix || ' ';
13601                                         CONTINUE;
13602                                 END;
13603                                 EXIT;
13604                         END LOOP;
13605                 END LOOP;
13606     EXCEPTION WHEN undefined_table THEN
13607         -- do nothing
13608     END;
13609     BEGIN
13610                 -- transfer folders the same way we transfer buckets (see above)
13611                 FOR folder_row in
13612                         SELECT id, name
13613                         FROM   reporter.output_folder
13614                         WHERE  owner = src_usr
13615                 LOOP
13616                         suffix := ' (' || src_usr || ')';
13617                         LOOP
13618                                 BEGIN
13619                                         UPDATE  reporter.output_folder
13620                                         SET     owner = dest_usr, name = name || suffix
13621                                         WHERE   id = folder_row.id;
13622                                 EXCEPTION WHEN unique_violation THEN
13623                                         suffix := suffix || ' ';
13624                                         CONTINUE;
13625                                 END;
13626                                 EXIT;
13627                         END LOOP;
13628                 END LOOP;
13629     EXCEPTION WHEN undefined_table THEN
13630         -- do nothing
13631     END;
13632
13633     -- Finally, delete the source user
13634     DELETE FROM actor.usr WHERE id = src_usr;
13635
13636 END;
13637 $$ LANGUAGE plpgsql;
13638
13639 -- The "add" trigger functions should protect against existing NULLed values, just in case
13640 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13641 BEGIN
13642     IF NOT NEW.voided THEN
13643         UPDATE  money.materialized_billable_xact_summary
13644           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13645             last_billing_ts = NEW.billing_ts,
13646             last_billing_note = NEW.note,
13647             last_billing_type = NEW.billing_type,
13648             balance_owed = balance_owed + NEW.amount
13649           WHERE id = NEW.xact;
13650     END IF;
13651
13652     RETURN NEW;
13653 END;
13654 $$ LANGUAGE PLPGSQL;
13655
13656 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13657 BEGIN
13658     IF NOT NEW.voided THEN
13659         UPDATE  money.materialized_billable_xact_summary
13660           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13661             last_payment_ts = NEW.payment_ts,
13662             last_payment_note = NEW.note,
13663             last_payment_type = TG_ARGV[0],
13664             balance_owed = balance_owed - NEW.amount
13665           WHERE id = NEW.xact;
13666     END IF;
13667
13668     RETURN NEW;
13669 END;
13670 $$ LANGUAGE PLPGSQL;
13671
13672 -- Refresh the mat view with the corrected underlying view
13673 TRUNCATE money.materialized_billable_xact_summary;
13674 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13675
13676 -- Now redefine the view as a window onto the materialized view
13677 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13678     SELECT * FROM money.materialized_billable_xact_summary;
13679
13680 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13681     user_id    IN INTEGER,
13682     perm_code  IN TEXT
13683 )
13684 RETURNS SETOF INTEGER AS $$
13685 --
13686 -- Return a set of all the org units for which a given user has a given
13687 -- permission, granted directly (not through inheritance from a parent
13688 -- org unit).
13689 --
13690 -- The permissions apply to a minimum depth of the org unit hierarchy,
13691 -- for the org unit(s) to which the user is assigned.  (They also apply
13692 -- to the subordinates of those org units, but we don't report the
13693 -- subordinates here.)
13694 --
13695 -- For purposes of this function, the permission.usr_work_ou_map table
13696 -- defines which users belong to which org units.  I.e. we ignore the
13697 -- home_ou column of actor.usr.
13698 --
13699 -- The result set may contain duplicates, which should be eliminated
13700 -- by a DISTINCT clause.
13701 --
13702 DECLARE
13703     b_super       BOOLEAN;
13704     n_perm        INTEGER;
13705     n_min_depth   INTEGER;
13706     n_work_ou     INTEGER;
13707     n_curr_ou     INTEGER;
13708     n_depth       INTEGER;
13709     n_curr_depth  INTEGER;
13710 BEGIN
13711     --
13712     -- Check for superuser
13713     --
13714     SELECT INTO b_super
13715         super_user
13716     FROM
13717         actor.usr
13718     WHERE
13719         id = user_id;
13720     --
13721     IF NOT FOUND THEN
13722         return;             -- No user?  No permissions.
13723     ELSIF b_super THEN
13724         --
13725         -- Super user has all permissions everywhere
13726         --
13727         FOR n_work_ou IN
13728             SELECT
13729                 id
13730             FROM
13731                 actor.org_unit
13732             WHERE
13733                 parent_ou IS NULL
13734         LOOP
13735             RETURN NEXT n_work_ou;
13736         END LOOP;
13737         RETURN;
13738     END IF;
13739     --
13740     -- Translate the permission name
13741     -- to a numeric permission id
13742     --
13743     SELECT INTO n_perm
13744         id
13745     FROM
13746         permission.perm_list
13747     WHERE
13748         code = perm_code;
13749     --
13750     IF NOT FOUND THEN
13751         RETURN;               -- No such permission
13752     END IF;
13753     --
13754     -- Find the highest-level org unit (i.e. the minimum depth)
13755     -- to which the permission is applied for this user
13756     --
13757     -- This query is modified from the one in permission.usr_perms().
13758     --
13759     SELECT INTO n_min_depth
13760         min( depth )
13761     FROM    (
13762         SELECT depth
13763           FROM permission.usr_perm_map upm
13764          WHERE upm.usr = user_id
13765            AND (upm.perm = n_perm OR upm.perm = -1)
13766                     UNION
13767         SELECT  gpm.depth
13768           FROM  permission.grp_perm_map gpm
13769           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13770             AND gpm.grp IN (
13771                SELECT   (permission.grp_ancestors(
13772                     (SELECT profile FROM actor.usr WHERE id = user_id)
13773                 )).id
13774             )
13775                     UNION
13776         SELECT  p.depth
13777           FROM  permission.grp_perm_map p
13778           WHERE (p.perm = n_perm OR p.perm = -1)
13779             AND p.grp IN (
13780                 SELECT (permission.grp_ancestors(m.grp)).id
13781                 FROM   permission.usr_grp_map m
13782                 WHERE  m.usr = user_id
13783             )
13784     ) AS x;
13785     --
13786     IF NOT FOUND THEN
13787         RETURN;                -- No such permission for this user
13788     END IF;
13789     --
13790     -- Identify the org units to which the user is assigned.  Note that
13791     -- we pay no attention to the home_ou column in actor.usr.
13792     --
13793     FOR n_work_ou IN
13794         SELECT
13795             work_ou
13796         FROM
13797             permission.usr_work_ou_map
13798         WHERE
13799             usr = user_id
13800     LOOP            -- For each org unit to which the user is assigned
13801         --
13802         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13803         -- We take it on faith that this depth agrees with the actual hierarchy
13804         -- defined in actor.org_unit.
13805         --
13806         SELECT INTO n_depth
13807             type.depth
13808         FROM
13809             actor.org_unit_type type
13810                 INNER JOIN actor.org_unit ou
13811                     ON ( ou.ou_type = type.id )
13812         WHERE
13813             ou.id = n_work_ou;
13814         --
13815         IF NOT FOUND THEN
13816             CONTINUE;        -- Maybe raise exception?
13817         END IF;
13818         --
13819         -- Compare the depth of the work org unit to the
13820         -- minimum depth, and branch accordingly
13821         --
13822         IF n_depth = n_min_depth THEN
13823             --
13824             -- The org unit is at the right depth, so return it.
13825             --
13826             RETURN NEXT n_work_ou;
13827         ELSIF n_depth > n_min_depth THEN
13828             --
13829             -- Traverse the org unit tree toward the root,
13830             -- until you reach the minimum depth determined above
13831             --
13832             n_curr_depth := n_depth;
13833             n_curr_ou := n_work_ou;
13834             WHILE n_curr_depth > n_min_depth LOOP
13835                 SELECT INTO n_curr_ou
13836                     parent_ou
13837                 FROM
13838                     actor.org_unit
13839                 WHERE
13840                     id = n_curr_ou;
13841                 --
13842                 IF FOUND THEN
13843                     n_curr_depth := n_curr_depth - 1;
13844                 ELSE
13845                     --
13846                     -- This can happen only if the hierarchy defined in
13847                     -- actor.org_unit is corrupted, or out of sync with
13848                     -- the depths defined in actor.org_unit_type.
13849                     -- Maybe we should raise an exception here, instead
13850                     -- of silently ignoring the problem.
13851                     --
13852                     n_curr_ou = NULL;
13853                     EXIT;
13854                 END IF;
13855             END LOOP;
13856             --
13857             IF n_curr_ou IS NOT NULL THEN
13858                 RETURN NEXT n_curr_ou;
13859             END IF;
13860         ELSE
13861             --
13862             -- The permission applies only at a depth greater than the work org unit.
13863             -- Use connectby() to find all dependent org units at the specified depth.
13864             --
13865             FOR n_curr_ou IN
13866                 SELECT ou::INTEGER
13867                 FROM connectby(
13868                         'actor.org_unit',         -- table name
13869                         'id',                     -- key column
13870                         'parent_ou',              -- recursive foreign key
13871                         n_work_ou::TEXT,          -- id of starting point
13872                         (n_min_depth - n_depth)   -- max depth to search, relative
13873                     )                             --   to starting point
13874                     AS t(
13875                         ou text,            -- dependent org unit
13876                         parent_ou text,     -- (ignore)
13877                         level int           -- depth relative to starting point
13878                     )
13879                 WHERE
13880                     level = n_min_depth - n_depth
13881             LOOP
13882                 RETURN NEXT n_curr_ou;
13883             END LOOP;
13884         END IF;
13885         --
13886     END LOOP;
13887     --
13888     RETURN;
13889     --
13890 END;
13891 $$ LANGUAGE 'plpgsql';
13892
13893 ALTER TABLE acq.purchase_order
13894         ADD COLUMN cancel_reason INT
13895                 REFERENCES acq.cancel_reason( id )
13896             DEFERRABLE INITIALLY DEFERRED,
13897         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
13898
13899 -- Build the history table and lifecycle view
13900 -- for acq.purchase_order
13901
13902 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
13903
13904 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
13905
13906 ALTER TABLE acq.lineitem
13907         ADD COLUMN cancel_reason INT
13908                 REFERENCES acq.cancel_reason( id )
13909             DEFERRABLE INITIALLY DEFERRED,
13910         ADD COLUMN estimated_unit_price NUMERIC,
13911         ADD COLUMN claim_policy INT
13912                 REFERENCES acq.claim_policy
13913                 DEFERRABLE INITIALLY DEFERRED,
13914         ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
13915
13916 -- Build the history table and lifecycle view
13917 -- for acq.lineitem
13918
13919 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
13920 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
13921
13922 ALTER TABLE acq.lineitem_detail
13923         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
13924                                             DEFERRABLE INITIALLY DEFERRED;
13925
13926 ALTER TABLE acq.lineitem_detail
13927         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
13928
13929 ALTER TABLE acq.lineitem_detail
13930         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13931                 ON DELETE CASCADE
13932                 DEFERRABLE INITIALLY DEFERRED;
13933
13934 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
13935
13936 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
13937         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
13938
13939 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
13940         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
13941
13942 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
13943
13944     use MARC::Record;
13945     use MARC::File::XML (BinaryEncoding => 'UTF-8');
13946     use strict;
13947
13948     my $target_xml = shift;
13949     my $source_xml = shift;
13950     my $field_spec = shift;
13951
13952     my $target_r = MARC::Record->new_from_xml( $target_xml );
13953     my $source_r = MARC::Record->new_from_xml( $source_xml );
13954
13955     return $target_xml unless ($target_r && $source_r);
13956
13957     my @field_list = split(',', $field_spec);
13958
13959     my %fields;
13960     for my $f (@field_list) {
13961         $f =~ s/^\s*//; $f =~ s/\s*$//;
13962         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
13963             my $field = $1;
13964             $field =~ s/\s+//;
13965             my $sf = $2;
13966             $sf =~ s/\s+//;
13967             my $match = $3;
13968             $match =~ s/^\s*//; $match =~ s/\s*$//;
13969             $fields{$field} = { sf => [ split('', $sf) ] };
13970             if ($match) {
13971                 my ($msf,$mre) = split('~', $match);
13972                 if (length($msf) > 0 and length($mre) > 0) {
13973                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
13974                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
13975                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
13976                 }
13977             }
13978         }
13979     }
13980
13981     for my $f ( keys %fields) {
13982         if ( @{$fields{$f}{sf}} ) {
13983             for my $from_field ($source_r->field( $f )) {
13984                 my @tos = $target_r->field( $f );
13985                 if (!@tos) {
13986                     next if (exists($fields{$f}{match}));
13987                     my @new_fields = map { $_->clone } $source_r->field( $f );
13988                     $target_r->insert_fields_ordered( @new_fields );
13989                 } else {
13990                     for my $to_field (@tos) {
13991                         if (exists($fields{$f}{match})) {
13992                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
13993                         }
13994                         my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
13995                         $to_field->add_subfields( @new_sf );
13996                     }
13997                 }
13998             }
13999         } else {
14000             my @new_fields = map { $_->clone } $source_r->field( $f );
14001             $target_r->insert_fields_ordered( @new_fields );
14002         }
14003     }
14004
14005     $target_xml = $target_r->as_xml_record;
14006     $target_xml =~ s/^<\?.+?\?>$//mo;
14007     $target_xml =~ s/\n//sgo;
14008     $target_xml =~ s/>\s+</></sgo;
14009
14010     return $target_xml;
14011
14012 $_$ LANGUAGE PLPERLU;
14013
14014 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14015
14016     use MARC::Record;
14017     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14018     use strict;
14019
14020     my $xml = shift;
14021     my $r = MARC::Record->new_from_xml( $xml );
14022
14023     return $xml unless ($r);
14024
14025     my $field_spec = shift;
14026     my @field_list = split(',', $field_spec);
14027
14028     my %fields;
14029     for my $f (@field_list) {
14030         $f =~ s/^\s*//; $f =~ s/\s*$//;
14031         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14032             my $field = $1;
14033             $field =~ s/\s+//;
14034             my $sf = $2;
14035             $sf =~ s/\s+//;
14036             my $match = $3;
14037             $match =~ s/^\s*//; $match =~ s/\s*$//;
14038             $fields{$field} = { sf => [ split('', $sf) ] };
14039             if ($match) {
14040                 my ($msf,$mre) = split('~', $match);
14041                 if (length($msf) > 0 and length($mre) > 0) {
14042                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14043                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14044                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14045                 }
14046             }
14047         }
14048     }
14049
14050     for my $f ( keys %fields) {
14051         for my $to_field ($r->field( $f )) {
14052             if (exists($fields{$f}{match})) {
14053                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14054             }
14055
14056             if ( @{$fields{$f}{sf}} ) {
14057                 $to_field->delete_subfield(code => $fields{$f}{sf});
14058             } else {
14059                 $r->delete_field( $to_field );
14060             }
14061         }
14062     }
14063
14064     $xml = $r->as_xml_record;
14065     $xml =~ s/^<\?.+?\?>$//mo;
14066     $xml =~ s/\n//sgo;
14067     $xml =~ s/>\s+</></sgo;
14068
14069     return $xml;
14070
14071 $_$ LANGUAGE PLPERLU;
14072
14073 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14074     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
14075 $_$ LANGUAGE SQL;
14076
14077 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14078     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
14079 $_$ LANGUAGE SQL;
14080
14081 CREATE VIEW action.unfulfilled_hold_max_loop AS
14082         SELECT  hold,
14083                 max(count) AS max
14084         FROM    action.unfulfilled_hold_loops
14085         GROUP BY 1;
14086
14087 ALTER TABLE acq.lineitem_attr
14088         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
14089
14090 ALTER TABLE acq.lineitem_attr
14091         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14092                 ON DELETE CASCADE
14093                 DEFERRABLE INITIALLY DEFERRED;
14094
14095 ALTER TABLE acq.po_note
14096         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
14097
14098 CREATE TABLE vandelay.merge_profile (
14099     id              BIGSERIAL   PRIMARY KEY,
14100     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
14101     name            TEXT        NOT NULL,
14102     add_spec        TEXT,
14103     replace_spec    TEXT,
14104     strip_spec      TEXT,
14105     preserve_spec   TEXT,
14106     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14107     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))
14108 );
14109
14110 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14111 DECLARE
14112     attr        RECORD;
14113     attr_def    RECORD;
14114     eg_rec      RECORD;
14115     id_value    TEXT;
14116     exact_id    BIGINT;
14117 BEGIN
14118
14119     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14120
14121     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14122
14123     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14124         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14125
14126         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14127             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14128             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14129             IF exact_id IS NOT NULL THEN
14130                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14131             END IF;
14132         END IF;
14133     END IF;
14134
14135     IF exact_id IS NULL THEN
14136         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
14137
14138             -- All numbers? check for an id match
14139             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14140                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14141                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14142                 END LOOP;
14143             END IF;
14144
14145             -- Looks like an ISBN? check for an isbn match
14146             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14147                 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
14148                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14149                     IF FOUND THEN
14150                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14151                     END IF;
14152                 END LOOP;
14153
14154                 -- subcheck for isbn-as-tcn
14155                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14156                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14157                 END LOOP;
14158             END IF;
14159
14160             -- check for an OCLC tcn_value match
14161             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14162                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14163                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14164                 END LOOP;
14165             END IF;
14166
14167             -- check for a direct tcn_value match
14168             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14169                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14170             END LOOP;
14171
14172             -- check for a direct item barcode match
14173             FOR eg_rec IN
14174                     SELECT  DISTINCT b.*
14175                       FROM  biblio.record_entry b
14176                             JOIN asset.call_number cn ON (cn.record = b.id)
14177                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14178                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14179             LOOP
14180                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14181             END LOOP;
14182
14183         END LOOP;
14184     END IF;
14185
14186     RETURN NULL;
14187 END;
14188 $func$ LANGUAGE PLPGSQL;
14189
14190 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 $_$
14191     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14192 $_$ LANGUAGE SQL;
14193
14194 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14195 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14196 DECLARE
14197     output              vandelay.compile_profile%ROWTYPE;
14198     profile             vandelay.merge_profile%ROWTYPE;
14199     profile_tmpl        TEXT;
14200     profile_tmpl_owner  TEXT;
14201     add_rule            TEXT := '';
14202     strip_rule          TEXT := '';
14203     replace_rule        TEXT := '';
14204     preserve_rule       TEXT := '';
14205
14206 BEGIN
14207
14208     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14209     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14210
14211     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14212         SELECT  p.* INTO profile
14213           FROM  vandelay.merge_profile p
14214                 JOIN actor.org_unit u ON (u.id = p.owner)
14215           WHERE p.name = profile_tmpl
14216                 AND u.shortname = profile_tmpl_owner;
14217
14218         IF profile.id IS NOT NULL THEN
14219             add_rule := COALESCE(profile.add_spec,'');
14220             strip_rule := COALESCE(profile.strip_spec,'');
14221             replace_rule := COALESCE(profile.replace_spec,'');
14222             preserve_rule := COALESCE(profile.preserve_spec,'');
14223         END IF;
14224     END IF;
14225
14226     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
14227     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
14228     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
14229     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
14230
14231     output.add_rule := BTRIM(add_rule,',');
14232     output.replace_rule := BTRIM(replace_rule,',');
14233     output.strip_rule := BTRIM(strip_rule,',');
14234     output.preserve_rule := BTRIM(preserve_rule,',');
14235
14236     RETURN output;
14237 END;
14238 $_$ LANGUAGE PLPGSQL;
14239
14240 -- Template-based marc munging functions
14241 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14242 DECLARE
14243     merge_profile   vandelay.merge_profile%ROWTYPE;
14244     dyn_profile     vandelay.compile_profile%ROWTYPE;
14245     editor_string   TEXT;
14246     editor_id       INT;
14247     source_marc     TEXT;
14248     target_marc     TEXT;
14249     eg_marc         TEXT;
14250     replace_rule    TEXT;
14251     match_count     INT;
14252 BEGIN
14253
14254     SELECT  b.marc INTO eg_marc
14255       FROM  biblio.record_entry b
14256       WHERE b.id = eg_id
14257       LIMIT 1;
14258
14259     IF eg_marc IS NULL OR v_marc IS NULL THEN
14260         -- RAISE NOTICE 'no marc for template or bib record';
14261         RETURN FALSE;
14262     END IF;
14263
14264     dyn_profile := vandelay.compile_profile( v_marc );
14265
14266     IF merge_profile_id IS NOT NULL THEN
14267         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14268         IF FOUND THEN
14269             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14270             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14271             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14272             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14273         END IF;
14274     END IF;
14275
14276     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14277         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14278         RETURN FALSE;
14279     END IF;
14280
14281     IF dyn_profile.replace_rule <> '' THEN
14282         source_marc = v_marc;
14283         target_marc = eg_marc;
14284         replace_rule = dyn_profile.replace_rule;
14285     ELSE
14286         source_marc = eg_marc;
14287         target_marc = v_marc;
14288         replace_rule = dyn_profile.preserve_rule;
14289     END IF;
14290
14291     UPDATE  biblio.record_entry
14292       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14293       WHERE id = eg_id;
14294
14295     IF NOT FOUND THEN
14296         -- RAISE NOTICE 'update of biblio.record_entry failed';
14297         RETURN FALSE;
14298     END IF;
14299
14300     RETURN TRUE;
14301
14302 END;
14303 $$ LANGUAGE PLPGSQL;
14304
14305 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14306     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14307 $$ LANGUAGE SQL;
14308
14309 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14310 DECLARE
14311     merge_profile   vandelay.merge_profile%ROWTYPE;
14312     dyn_profile     vandelay.compile_profile%ROWTYPE;
14313     editor_string   TEXT;
14314     editor_id       INT;
14315     source_marc     TEXT;
14316     target_marc     TEXT;
14317     eg_marc         TEXT;
14318     v_marc          TEXT;
14319     replace_rule    TEXT;
14320     match_count     INT;
14321 BEGIN
14322
14323     SELECT  q.marc INTO v_marc
14324       FROM  vandelay.queued_record q
14325             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14326       LIMIT 1;
14327
14328     IF v_marc IS NULL THEN
14329         -- RAISE NOTICE 'no marc for vandelay or bib record';
14330         RETURN FALSE;
14331     END IF;
14332
14333     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14334         UPDATE  vandelay.queued_bib_record
14335           SET   imported_as = eg_id,
14336                 import_time = NOW()
14337           WHERE id = import_id;
14338
14339         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14340
14341         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14342             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14343
14344             IF editor_id IS NULL THEN
14345                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14346             END IF;
14347
14348             IF editor_id IS NOT NULL THEN
14349                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14350             END IF;
14351         END IF;
14352
14353         RETURN TRUE;
14354     END IF;
14355
14356     -- RAISE NOTICE 'update of biblio.record_entry failed';
14357
14358     RETURN FALSE;
14359
14360 END;
14361 $$ LANGUAGE PLPGSQL;
14362
14363 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14364 DECLARE
14365     eg_id           BIGINT;
14366     match_count     INT;
14367     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14368 BEGIN
14369
14370     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14371
14372     IF FOUND THEN
14373         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14374         RETURN FALSE;
14375     END IF;
14376
14377     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14378
14379     IF match_count <> 1 THEN
14380         -- RAISE NOTICE 'not an exact match';
14381         RETURN FALSE;
14382     END IF;
14383
14384     SELECT  d.* INTO match_attr
14385       FROM  vandelay.bib_attr_definition d
14386             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14387             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14388       WHERE m.queued_record = import_id;
14389
14390     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14391         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14392         RETURN FALSE;
14393     END IF;
14394
14395     SELECT  m.eg_record INTO eg_id
14396       FROM  vandelay.bib_match m
14397       WHERE m.queued_record = import_id
14398       LIMIT 1;
14399
14400     IF eg_id IS NULL THEN
14401         RETURN FALSE;
14402     END IF;
14403
14404     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14405 END;
14406 $$ LANGUAGE PLPGSQL;
14407
14408 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14409 DECLARE
14410     queued_record   vandelay.queued_bib_record%ROWTYPE;
14411 BEGIN
14412
14413     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14414
14415         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14416             RETURN NEXT queued_record.id;
14417         END IF;
14418
14419     END LOOP;
14420
14421     RETURN;
14422
14423 END;
14424 $$ LANGUAGE PLPGSQL;
14425
14426 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14427     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14428 $$ LANGUAGE SQL;
14429
14430 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14431 DECLARE
14432     merge_profile   vandelay.merge_profile%ROWTYPE;
14433     dyn_profile     vandelay.compile_profile%ROWTYPE;
14434     source_marc     TEXT;
14435     target_marc     TEXT;
14436     eg_marc         TEXT;
14437     v_marc          TEXT;
14438     replace_rule    TEXT;
14439     match_count     INT;
14440 BEGIN
14441
14442     SELECT  b.marc INTO eg_marc
14443       FROM  authority.record_entry b
14444             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14445       LIMIT 1;
14446
14447     SELECT  q.marc INTO v_marc
14448       FROM  vandelay.queued_record q
14449             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14450       LIMIT 1;
14451
14452     IF eg_marc IS NULL OR v_marc IS NULL THEN
14453         -- RAISE NOTICE 'no marc for vandelay or authority record';
14454         RETURN FALSE;
14455     END IF;
14456
14457     dyn_profile := vandelay.compile_profile( v_marc );
14458
14459     IF merge_profile_id IS NOT NULL THEN
14460         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14461         IF FOUND THEN
14462             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14463             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14464             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14465             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14466         END IF;
14467     END IF;
14468
14469     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14470         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14471         RETURN FALSE;
14472     END IF;
14473
14474     IF dyn_profile.replace_rule <> '' THEN
14475         source_marc = v_marc;
14476         target_marc = eg_marc;
14477         replace_rule = dyn_profile.replace_rule;
14478     ELSE
14479         source_marc = eg_marc;
14480         target_marc = v_marc;
14481         replace_rule = dyn_profile.preserve_rule;
14482     END IF;
14483
14484     UPDATE  authority.record_entry
14485       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14486       WHERE id = eg_id;
14487
14488     IF FOUND THEN
14489         UPDATE  vandelay.queued_authority_record
14490           SET   imported_as = eg_id,
14491                 import_time = NOW()
14492           WHERE id = import_id;
14493         RETURN TRUE;
14494     END IF;
14495
14496     -- RAISE NOTICE 'update of authority.record_entry failed';
14497
14498     RETURN FALSE;
14499
14500 END;
14501 $$ LANGUAGE PLPGSQL;
14502
14503 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14504 DECLARE
14505     eg_id           BIGINT;
14506     match_count     INT;
14507 BEGIN
14508     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14509
14510     IF match_count <> 1 THEN
14511         -- RAISE NOTICE 'not an exact match';
14512         RETURN FALSE;
14513     END IF;
14514
14515     SELECT  m.eg_record INTO eg_id
14516       FROM  vandelay.authority_match m
14517       WHERE m.queued_record = import_id
14518       LIMIT 1;
14519
14520     IF eg_id IS NULL THEN
14521         RETURN FALSE;
14522     END IF;
14523
14524     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14525 END;
14526 $$ LANGUAGE PLPGSQL;
14527
14528 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14529 DECLARE
14530     queued_record   vandelay.queued_authority_record%ROWTYPE;
14531 BEGIN
14532
14533     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14534
14535         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14536             RETURN NEXT queued_record.id;
14537         END IF;
14538
14539     END LOOP;
14540
14541     RETURN;
14542
14543 END;
14544 $$ LANGUAGE PLPGSQL;
14545
14546 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14547     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14548 $$ LANGUAGE SQL;
14549
14550 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14551 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14552 DECLARE
14553     eg_tcn          TEXT;
14554     eg_tcn_source   TEXT;
14555     output          vandelay.tcn_data%ROWTYPE;
14556 BEGIN
14557
14558     -- 001/003
14559     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14560     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14561
14562         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14563         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14564             eg_tcn_source := 'System Local';
14565         END IF;
14566
14567         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14568
14569         IF NOT FOUND THEN
14570             output.used := FALSE;
14571         ELSE
14572             output.used := TRUE;
14573         END IF;
14574
14575         output.tcn := eg_tcn;
14576         output.tcn_source := eg_tcn_source;
14577         RETURN NEXT output;
14578
14579     END IF;
14580
14581     -- 901 ab
14582     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14583     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14584
14585         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14586         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14587             eg_tcn_source := 'System Local';
14588         END IF;
14589
14590         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14591
14592         IF NOT FOUND THEN
14593             output.used := FALSE;
14594         ELSE
14595             output.used := TRUE;
14596         END IF;
14597
14598         output.tcn := eg_tcn;
14599         output.tcn_source := eg_tcn_source;
14600         RETURN NEXT output;
14601
14602     END IF;
14603
14604     -- 039 ab
14605     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14606     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14607
14608         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14609         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14610             eg_tcn_source := 'System Local';
14611         END IF;
14612
14613         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14614
14615         IF NOT FOUND THEN
14616             output.used := FALSE;
14617         ELSE
14618             output.used := TRUE;
14619         END IF;
14620
14621         output.tcn := eg_tcn;
14622         output.tcn_source := eg_tcn_source;
14623         RETURN NEXT output;
14624
14625     END IF;
14626
14627     -- 020 a
14628     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14629     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14630
14631         eg_tcn_source := 'ISBN';
14632
14633         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14634
14635         IF NOT FOUND THEN
14636             output.used := FALSE;
14637         ELSE
14638             output.used := TRUE;
14639         END IF;
14640
14641         output.tcn := eg_tcn;
14642         output.tcn_source := eg_tcn_source;
14643         RETURN NEXT output;
14644
14645     END IF;
14646
14647     -- 022 a
14648     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14649     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14650
14651         eg_tcn_source := 'ISSN';
14652
14653         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14654
14655         IF NOT FOUND THEN
14656             output.used := FALSE;
14657         ELSE
14658             output.used := TRUE;
14659         END IF;
14660
14661         output.tcn := eg_tcn;
14662         output.tcn_source := eg_tcn_source;
14663         RETURN NEXT output;
14664
14665     END IF;
14666
14667     -- 010 a
14668     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14669     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14670
14671         eg_tcn_source := 'LCCN';
14672
14673         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14674
14675         IF NOT FOUND THEN
14676             output.used := FALSE;
14677         ELSE
14678             output.used := TRUE;
14679         END IF;
14680
14681         output.tcn := eg_tcn;
14682         output.tcn_source := eg_tcn_source;
14683         RETURN NEXT output;
14684
14685     END IF;
14686
14687     -- 035 a
14688     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
14689     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14690
14691         eg_tcn_source := 'System Legacy';
14692
14693         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14694
14695         IF NOT FOUND THEN
14696             output.used := FALSE;
14697         ELSE
14698             output.used := TRUE;
14699         END IF;
14700
14701         output.tcn := eg_tcn;
14702         output.tcn_source := eg_tcn_source;
14703         RETURN NEXT output;
14704
14705     END IF;
14706
14707     RETURN;
14708 END;
14709 $_$ LANGUAGE PLPGSQL;
14710
14711 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14712
14713 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);
14714
14715 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
14716
14717 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14718 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14719 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14720 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14721 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14722
14723 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14724 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14725 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14726 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14727
14728 ALTER TABLE metabib.series_field_entry
14729         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14730                 REFERENCES biblio.record_entry (id)
14731                 ON DELETE CASCADE
14732                 DEFERRABLE INITIALLY DEFERRED;
14733
14734 ALTER TABLE metabib.series_field_entry
14735         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14736                 REFERENCES config.metabib_field (id)
14737                 ON DELETE CASCADE
14738                 DEFERRABLE INITIALLY DEFERRED;
14739
14740 CREATE TABLE acq.claim_policy_action (
14741         id              SERIAL       PRIMARY KEY,
14742         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14743                                  ON DELETE CASCADE
14744                                      DEFERRABLE INITIALLY DEFERRED,
14745         action_interval INTERVAL     NOT NULL,
14746         action          INT          NOT NULL REFERENCES acq.claim_event_type
14747                                      DEFERRABLE INITIALLY DEFERRED,
14748         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14749 );
14750
14751 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14752 DECLARE
14753         value           TEXT;
14754         atype           TEXT;
14755         prov            INT;
14756         pos             INT;
14757         adef            RECORD;
14758         xpath_string    TEXT;
14759 BEGIN
14760         FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14761
14762                 SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14763
14764                 IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14765                         IF (atype = 'lineitem_provider_attr_definition') THEN
14766                                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14767                                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14768                         END IF;
14769                         
14770                         IF (atype = 'lineitem_provider_attr_definition') THEN
14771                                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14772                         ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14773                                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14774                         ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14775                                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14776                         END IF;
14777
14778             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14779
14780             IF (adef.code = 'title' OR adef.code = 'author') THEN
14781                 -- title and author should not be split
14782                 -- FIXME: once oils_xpath can grok XPATH 2.0 functions, we can use
14783                 -- string-join in the xpath and remove this special case
14784                         SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14785                         IF (value IS NOT NULL AND value <> '') THEN
14786                                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14787                                     VALUES (NEW.id, adef.id, atype, adef.code, value);
14788                 END IF;
14789             ELSE
14790                 pos := 1;
14791
14792                 LOOP
14793                             SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14794
14795                             IF (value IS NOT NULL AND value <> '') THEN
14796                                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14797                                             VALUES (NEW.id, adef.id, atype, adef.code, value);
14798                     ELSE
14799                         EXIT;
14800                                 END IF;
14801
14802                     pos := pos + 1;
14803                 END LOOP;
14804             END IF;
14805
14806                 END IF;
14807
14808         END LOOP;
14809
14810         RETURN NULL;
14811 END;
14812 $function$ LANGUAGE PLPGSQL;
14813
14814 UPDATE config.metabib_field SET label = name;
14815 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14816
14817 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
14818          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
14819
14820 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
14821
14822 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
14823
14824 CREATE TABLE config.metabib_search_alias (
14825     alias       TEXT    PRIMARY KEY,
14826     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
14827     field       INT     REFERENCES config.metabib_field (id)
14828 );
14829
14830 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
14831 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
14832 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
14833 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
14834 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
14835 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
14836 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
14837 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
14838
14839 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
14840 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
14841 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
14842 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
14843 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
14844 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
14845 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
14846 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
14847 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
14848 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
14849 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
14850 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
14851 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
14852
14853 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
14854 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
14855 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
14856 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
14857 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
14858 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
14859 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
14860 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
14861
14862 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
14863 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
14864 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
14865 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
14866 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
14867 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
14868
14869 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
14870 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
14871 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
14872
14873 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
14874 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;
14875 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;
14876 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;
14877 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;
14878
14879 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
14880 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
14881 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
14882 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
14883
14884 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
14885 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
14886 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
14887 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
14888 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
14889 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
14890 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
14891 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
14892 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
14893 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
14894 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
14895 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
14896 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
14897 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
14898
14899 CREATE TABLE asset.opac_visible_copies (
14900   id        BIGINT primary key, -- copy id
14901   record    BIGINT,
14902   circ_lib  INTEGER
14903 );
14904 COMMENT ON TABLE asset.opac_visible_copies IS $$
14905 Materialized view of copies that are visible in the OPAC, used by
14906 search.query_parser_fts() to speed up OPAC visibility checks on large
14907 databases.  Contents are maintained by a set of triggers.
14908 $$;
14909 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
14910
14911 CREATE OR REPLACE FUNCTION search.query_parser_fts (
14912
14913     param_search_ou INT,
14914     param_depth     INT,
14915     param_query     TEXT,
14916     param_statuses  INT[],
14917     param_locations INT[],
14918     param_offset    INT,
14919     param_check     INT,
14920     param_limit     INT,
14921     metarecord      BOOL,
14922     staff           BOOL
14923  
14924 ) RETURNS SETOF search.search_result AS $func$
14925 DECLARE
14926
14927     current_res         search.search_result%ROWTYPE;
14928     search_org_list     INT[];
14929
14930     check_limit         INT;
14931     core_limit          INT;
14932     core_offset         INT;
14933     tmp_int             INT;
14934
14935     core_result         RECORD;
14936     core_cursor         REFCURSOR;
14937     core_rel_query      TEXT;
14938
14939     total_count         INT := 0;
14940     check_count         INT := 0;
14941     deleted_count       INT := 0;
14942     visible_count       INT := 0;
14943     excluded_count      INT := 0;
14944
14945 BEGIN
14946
14947     check_limit := COALESCE( param_check, 1000 );
14948     core_limit  := COALESCE( param_limit, 25000 );
14949     core_offset := COALESCE( param_offset, 0 );
14950
14951     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
14952
14953     IF param_search_ou > 0 THEN
14954         IF param_depth IS NOT NULL THEN
14955             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
14956         ELSE
14957             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
14958         END IF;
14959     ELSIF param_search_ou < 0 THEN
14960         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
14961     ELSIF param_search_ou = 0 THEN
14962         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
14963     END IF;
14964
14965     OPEN core_cursor FOR EXECUTE param_query;
14966
14967     LOOP
14968
14969         FETCH core_cursor INTO core_result;
14970         EXIT WHEN NOT FOUND;
14971         EXIT WHEN total_count >= core_limit;
14972
14973         total_count := total_count + 1;
14974
14975         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
14976
14977         check_count := check_count + 1;
14978
14979         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
14980         IF NOT FOUND THEN
14981             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
14982             deleted_count := deleted_count + 1;
14983             CONTINUE;
14984         END IF;
14985
14986         PERFORM 1
14987           FROM  biblio.record_entry b
14988                 JOIN config.bib_source s ON (b.source = s.id)
14989           WHERE s.transcendant
14990                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
14991
14992         IF FOUND THEN
14993             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
14994             visible_count := visible_count + 1;
14995
14996             current_res.id = core_result.id;
14997             current_res.rel = core_result.rel;
14998
14999             tmp_int := 1;
15000             IF metarecord THEN
15001                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15002             END IF;
15003
15004             IF tmp_int = 1 THEN
15005                 current_res.record = core_result.records[1];
15006             ELSE
15007                 current_res.record = NULL;
15008             END IF;
15009
15010             RETURN NEXT current_res;
15011
15012             CONTINUE;
15013         END IF;
15014
15015         PERFORM 1
15016           FROM  asset.call_number cn
15017                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
15018                 JOIN asset.uri uri ON (map.uri = uri.id)
15019           WHERE NOT cn.deleted
15020                 AND cn.label = '##URI##'
15021                 AND uri.active
15022                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
15023                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15024                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15025           LIMIT 1;
15026
15027         IF FOUND THEN
15028             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
15029             visible_count := visible_count + 1;
15030
15031             current_res.id = core_result.id;
15032             current_res.rel = core_result.rel;
15033
15034             tmp_int := 1;
15035             IF metarecord THEN
15036                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15037             END IF;
15038
15039             IF tmp_int = 1 THEN
15040                 current_res.record = core_result.records[1];
15041             ELSE
15042                 current_res.record = NULL;
15043             END IF;
15044
15045             RETURN NEXT current_res;
15046
15047             CONTINUE;
15048         END IF;
15049
15050         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
15051
15052             PERFORM 1
15053               FROM  asset.call_number cn
15054                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15055               WHERE NOT cn.deleted
15056                     AND NOT cp.deleted
15057                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
15058                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15059                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15060               LIMIT 1;
15061
15062             IF NOT FOUND THEN
15063                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
15064                 excluded_count := excluded_count + 1;
15065                 CONTINUE;
15066             END IF;
15067
15068         END IF;
15069
15070         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
15071
15072             PERFORM 1
15073               FROM  asset.call_number cn
15074                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15075               WHERE NOT cn.deleted
15076                     AND NOT cp.deleted
15077                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
15078                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15079                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15080               LIMIT 1;
15081
15082             IF NOT FOUND THEN
15083                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
15084                 excluded_count := excluded_count + 1;
15085                 CONTINUE;
15086             END IF;
15087
15088         END IF;
15089
15090         IF staff IS NULL OR NOT staff THEN
15091
15092             PERFORM 1
15093               FROM  asset.opac_visible_copies
15094               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15095                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15096               LIMIT 1;
15097
15098             IF NOT FOUND THEN
15099                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15100                 excluded_count := excluded_count + 1;
15101                 CONTINUE;
15102             END IF;
15103
15104         ELSE
15105
15106             PERFORM 1
15107               FROM  asset.call_number cn
15108                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15109                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15110               WHERE NOT cn.deleted
15111                     AND NOT cp.deleted
15112                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15113                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15114               LIMIT 1;
15115
15116             IF NOT FOUND THEN
15117
15118                 PERFORM 1
15119                   FROM  asset.call_number cn
15120                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15121                   LIMIT 1;
15122
15123                 IF FOUND THEN
15124                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15125                     excluded_count := excluded_count + 1;
15126                     CONTINUE;
15127                 END IF;
15128
15129             END IF;
15130
15131         END IF;
15132
15133         visible_count := visible_count + 1;
15134
15135         current_res.id = core_result.id;
15136         current_res.rel = core_result.rel;
15137
15138         tmp_int := 1;
15139         IF metarecord THEN
15140             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15141         END IF;
15142
15143         IF tmp_int = 1 THEN
15144             current_res.record = core_result.records[1];
15145         ELSE
15146             current_res.record = NULL;
15147         END IF;
15148
15149         RETURN NEXT current_res;
15150
15151         IF visible_count % 1000 = 0 THEN
15152             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15153         END IF;
15154
15155     END LOOP;
15156
15157     current_res.id = NULL;
15158     current_res.rel = NULL;
15159     current_res.record = NULL;
15160     current_res.total = total_count;
15161     current_res.checked = check_count;
15162     current_res.deleted = deleted_count;
15163     current_res.visible = visible_count;
15164     current_res.excluded = excluded_count;
15165
15166     CLOSE core_cursor;
15167
15168     RETURN NEXT current_res;
15169
15170 END;
15171 $func$ LANGUAGE PLPGSQL;
15172
15173 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15174 ALTER TABLE biblio.record_entry
15175          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15176          REFERENCES actor.org_unit (id)
15177          DEFERRABLE INITIALLY DEFERRED;
15178
15179 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15180
15181 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15182 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15183
15184 DROP VIEW auditor.biblio_record_entry_lifecycle;
15185
15186 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15187
15188 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15189         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15190 $$ LANGUAGE SQL STRICT IMMUTABLE;
15191
15192 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15193     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15194 $$ LANGUAGE SQL STRICT IMMUTABLE;
15195
15196 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15197     return lc(shift);
15198 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15199
15200 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15201     return uc(shift);
15202 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15203
15204 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15205     use Unicode::Normalize;
15206
15207     my $x = NFD(shift);
15208     $x =~ s/\pM+//go;
15209     return $x;
15210
15211 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15212
15213 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15214     use Unicode::Normalize;
15215
15216     my $x = NFC(shift);
15217     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15218     return $x;
15219
15220 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15221
15222 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15223 DECLARE
15224     setting RECORD;
15225     cur_org INT;
15226 BEGIN
15227     cur_org := org_id;
15228     LOOP
15229         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15230         IF FOUND THEN
15231             RETURN NEXT setting;
15232         END IF;
15233         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15234         EXIT WHEN cur_org IS NULL;
15235     END LOOP;
15236     RETURN;
15237 END;
15238 $$ LANGUAGE plpgsql STABLE;
15239
15240 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15241 DECLARE
15242     counter INT;
15243     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15244 BEGIN
15245
15246     SELECT  COUNT(*) INTO counter
15247       FROM  oils_xpath_table(
15248                 'id',
15249                 'marc',
15250                 'acq.lineitem',
15251                 '//*[@tag="' || tag || '"]',
15252                 'id=' || lineitem
15253             ) as t(i int,c text);
15254
15255     FOR i IN 1 .. counter LOOP
15256         FOR lida IN
15257             SELECT  *
15258               FROM  (   SELECT  id,i,t,v
15259                           FROM  oils_xpath_table(
15260                                     'id',
15261                                     'marc',
15262                                     'acq.lineitem',
15263                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15264                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15265                                     'id=' || lineitem
15266                                 ) as t(id int,t text,v text)
15267                     )x
15268         LOOP
15269             RETURN NEXT lida;
15270         END LOOP;
15271     END LOOP;
15272
15273     RETURN;
15274 END;
15275 $$ LANGUAGE PLPGSQL;
15276
15277 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15278 DECLARE
15279     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15280     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15281     result      config.i18n_core%ROWTYPE;
15282     fallback    TEXT;
15283     keyfield    TEXT := keyclass || '.' || keycol;
15284 BEGIN
15285
15286     -- Try the full locale
15287     SELECT  * INTO result
15288       FROM  config.i18n_core
15289       WHERE fq_field = keyfield
15290             AND identity_value = keyvalue
15291             AND translation = locale;
15292
15293     -- Try just the language
15294     IF NOT FOUND THEN
15295         SELECT  * INTO result
15296           FROM  config.i18n_core
15297           WHERE fq_field = keyfield
15298                 AND identity_value = keyvalue
15299                 AND translation = language;
15300     END IF;
15301
15302     -- Fall back to the string we passed in in the first place
15303     IF NOT FOUND THEN
15304     EXECUTE
15305             'SELECT ' ||
15306                 keycol ||
15307             ' FROM ' || keytable ||
15308             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15309                 INTO fallback;
15310         RETURN fallback;
15311     END IF;
15312
15313     RETURN result.string;
15314 END;
15315 $func$ LANGUAGE PLPGSQL STABLE;
15316
15317 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15318
15319 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15320
15321 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15322
15323 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15324     3, 1, 'delivered_but_lost',
15325     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15326
15327 CREATE TABLE config.global_flag (
15328     label   TEXT    NOT NULL
15329 ) INHERITS (config.internal_flag);
15330 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15331
15332 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
15333     VALUES (
15334         'cat.bib.use_id_for_tcn',
15335         oils_i18n_gettext(
15336             'cat.bib.use_id_for_tcn',
15337             'Cat: Use Internal ID for TCN Value',
15338             'cgf', 
15339             'label'
15340         )
15341     );
15342
15343 -- resolves performance issue noted by EG Indiana
15344
15345 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15346
15347 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15348
15349 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15350     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15351 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15352     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15353 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15354     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15355 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15356     (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 );
15357 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15358     (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 );
15359 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15360     (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 );
15361 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15362     (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 );
15363 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15364     (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 );
15365 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15366     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15367
15368 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15369  
15370
15371 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15372
15373 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15374 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15375 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15376 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15377 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15378 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15379
15380 CREATE TABLE metabib.identifier_field_entry (
15381         id              BIGSERIAL       PRIMARY KEY,
15382         source          BIGINT          NOT NULL,
15383         field           INT             NOT NULL,
15384         value           TEXT            NOT NULL,
15385         index_vector    tsvector        NOT NULL
15386 );
15387 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15388         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15389         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15390
15391 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15392 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15393     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15394 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15395
15396 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15397     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15398 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15399     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15400
15401 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15402     use Business::ISBN;
15403     use strict;
15404     use warnings;
15405
15406     # For each ISBN found in a single string containing a set of ISBNs:
15407     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15408     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15409
15410     my $input = shift;
15411     my $output = '';
15412
15413     foreach my $word (split(/\s/, $input)) {
15414         my $isbn = Business::ISBN->new($word);
15415
15416         # First check the checksum; if it is not valid, fix it and add the original
15417         # bad-checksum ISBN to the output
15418         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15419             $output .= $isbn->isbn() . " ";
15420             $isbn->fix_checksum();
15421         }
15422
15423         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15424         # and add the normalized original ISBN to the output
15425         if ($isbn && $isbn->is_valid()) {
15426             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15427             $output .= $isbn->isbn . " ";
15428
15429             # If we successfully converted the ISBN to its counterpart, add the
15430             # converted ISBN to the output as well
15431             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15432         }
15433     }
15434     return $output if $output;
15435
15436     # If there were no valid ISBNs, just return the raw input
15437     return $input;
15438 $func$ LANGUAGE PLPERLU;
15439
15440 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15441 /*
15442  * Copyright (C) 2010 Merrimack Valley Library Consortium
15443  * Jason Stephenson <jstephenson@mvlc.org>
15444  * Copyright (C) 2010 Laurentian University
15445  * Dan Scott <dscott@laurentian.ca>
15446  *
15447  * The translate_isbn1013 function takes an input ISBN and returns the
15448  * following in a single space-delimited string if the input ISBN is valid:
15449  *   - The normalized input ISBN (hyphens stripped)
15450  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15451  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15452  */
15453 $$;
15454
15455 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15456 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15457 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15458 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15459 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15460 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15461
15462 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15463         'ISBN 10/13 conversion',
15464         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15465         'translate_isbn1013',
15466         0
15467 );
15468
15469 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15470         'Replace',
15471         'Replace all occurences of first parameter in the string with the second parameter.',
15472         'replace',
15473         2
15474 );
15475
15476 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15477     SELECT  m.id, i.id, 1
15478       FROM  config.metabib_field m,
15479             config.index_normalizer i
15480       WHERE i.func IN ('first_word')
15481             AND m.id IN (18);
15482
15483 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15484     SELECT  m.id, i.id, 2
15485       FROM  config.metabib_field m,
15486             config.index_normalizer i
15487       WHERE i.func IN ('translate_isbn1013')
15488             AND m.id IN (18);
15489
15490 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15491     SELECT  m.id, i.id, $$['-','']$$
15492       FROM  config.metabib_field m,
15493             config.index_normalizer i
15494       WHERE i.func IN ('replace')
15495             AND m.id IN (19);
15496
15497 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15498     SELECT  m.id, i.id, $$[' ','']$$
15499       FROM  config.metabib_field m,
15500             config.index_normalizer i
15501       WHERE i.func IN ('replace')
15502             AND m.id IN (19);
15503
15504 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15505
15506 UPDATE  config.metabib_field_index_norm_map
15507   SET   params = REPLACE(params,E'\'','"')
15508   WHERE params IS NOT NULL AND params <> '';
15509
15510 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15511
15512 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15513
15514 ALTER TABLE config.circ_modifier
15515         ADD COLUMN avg_wait_time INTERVAL;
15516
15517 --CREATE TABLE actor.usr_password_reset (
15518 --  id SERIAL PRIMARY KEY,
15519 --  uuid TEXT NOT NULL, 
15520 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15521 --  request_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), 
15522 --  has_been_reset BOOL NOT NULL DEFAULT false
15523 --);
15524 --COMMENT ON TABLE actor.usr_password_reset IS $$
15525 --/*
15526 -- * Copyright (C) 2010 Laurentian University
15527 -- * Dan Scott <dscott@laurentian.ca>
15528 -- *
15529 -- * Self-serve password reset requests
15530 -- *
15531 -- * ****
15532 -- *
15533 -- * This program is free software; you can redistribute it and/or
15534 -- * modify it under the terms of the GNU General Public License
15535 -- * as published by the Free Software Foundation; either version 2
15536 -- * of the License, or (at your option) any later version.
15537 -- *
15538 -- * This program is distributed in the hope that it will be useful,
15539 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15540 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15541 -- * GNU General Public License for more details.
15542 -- */
15543 --$$;
15544 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15545 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15546 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15547 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15548
15549 -- Use the identifier search class tsconfig
15550 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15551 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15552     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15553     FOR EACH ROW
15554     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15555
15556 INSERT INTO config.global_flag (name,label,enabled)
15557     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15558 INSERT INTO config.global_flag (name,label,enabled)
15559     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15560
15561 -- turn a JSON scalar into an SQL TEXT value
15562 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15563     use JSON::XS;                    
15564     my $json = shift();
15565     my $txt;
15566     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15567     return undef if ($@);
15568     return $txt
15569 $f$ LANGUAGE PLPERLU;
15570
15571 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15572 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15573 DECLARE
15574     c               action.circulation%ROWTYPE;
15575     view_age        INTERVAL;
15576     usr_view_age    actor.usr_setting%ROWTYPE;
15577     usr_view_start  actor.usr_setting%ROWTYPE;
15578 BEGIN
15579     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15580     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15581
15582     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15583         -- User opted in and supplied a retention age
15584         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15585             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15586         ELSE
15587             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15588         END IF;
15589     ELSIF usr_view_start.value IS NOT NULL THEN
15590         -- User opted in
15591         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15592     ELSE
15593         -- User did not opt in
15594         RETURN;
15595     END IF;
15596
15597     FOR c IN
15598         SELECT  *
15599           FROM  action.circulation
15600           WHERE usr = usr_id
15601                 AND parent_circ IS NULL
15602                 AND xact_start > NOW() - view_age
15603           ORDER BY xact_start
15604     LOOP
15605         RETURN NEXT c;
15606     END LOOP;
15607
15608     RETURN;
15609 END;
15610 $func$ LANGUAGE PLPGSQL;
15611
15612 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15613 DECLARE
15614     usr_keep_age    actor.usr_setting%ROWTYPE;
15615     usr_keep_start  actor.usr_setting%ROWTYPE;
15616     org_keep_age    INTERVAL;
15617     org_keep_count  INT;
15618
15619     keep_age        INTERVAL;
15620
15621     target_acp      RECORD;
15622     circ_chain_head action.circulation%ROWTYPE;
15623     circ_chain_tail action.circulation%ROWTYPE;
15624
15625     purge_position  INT;
15626     count_purged    INT;
15627 BEGIN
15628
15629     count_purged := 0;
15630
15631     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15632
15633     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15634     IF org_keep_count IS NULL THEN
15635         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15636     END IF;
15637
15638     -- First, find copies with more than keep_count non-renewal circs
15639     FOR target_acp IN
15640         SELECT  target_copy,
15641                 COUNT(*) AS total_real_circs
15642           FROM  action.circulation
15643           WHERE parent_circ IS NULL
15644                 AND xact_finish IS NOT NULL
15645           GROUP BY target_copy
15646           HAVING COUNT(*) > org_keep_count
15647     LOOP
15648         purge_position := 0;
15649         -- And, for those, select circs that are finished and older than keep_age
15650         FOR circ_chain_head IN
15651             SELECT  *
15652               FROM  action.circulation
15653               WHERE target_copy = target_acp.target_copy
15654                     AND parent_circ IS NULL
15655               ORDER BY xact_start
15656         LOOP
15657
15658             -- Stop once we've purged enough circs to hit org_keep_count
15659             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15660
15661             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15662             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15663
15664             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15665             usr_keep_age.value := NULL;
15666             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15667
15668             usr_keep_start.value := NULL;
15669             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15670
15671             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15672                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15673                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15674                 ELSE
15675                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15676                 END IF;
15677             ELSIF usr_keep_start.value IS NOT NULL THEN
15678                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15679             ELSE
15680                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15681             END IF;
15682
15683             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15684
15685             -- We've passed the purging tests, purge the circ chain starting at the end
15686             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15687             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15688                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15689                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15690             END LOOP;
15691
15692             count_purged := count_purged + 1;
15693             purge_position := purge_position + 1;
15694
15695         END LOOP;
15696     END LOOP;
15697 END;
15698 $func$ LANGUAGE PLPGSQL;
15699
15700 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15701 DECLARE
15702     h               action.hold_request%ROWTYPE;
15703     view_age        INTERVAL;
15704     view_count      INT;
15705     usr_view_count  actor.usr_setting%ROWTYPE;
15706     usr_view_age    actor.usr_setting%ROWTYPE;
15707     usr_view_start  actor.usr_setting%ROWTYPE;
15708 BEGIN
15709     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15710     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15711     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15712
15713     FOR h IN
15714         SELECT  *
15715           FROM  action.hold_request
15716           WHERE usr = usr_id
15717                 AND fulfillment_time IS NULL
15718                 AND cancel_time IS NULL
15719           ORDER BY request_time DESC
15720     LOOP
15721         RETURN NEXT h;
15722     END LOOP;
15723
15724     IF usr_view_start.value IS NULL THEN
15725         RETURN;
15726     END IF;
15727
15728     IF usr_view_age.value IS NOT NULL THEN
15729         -- User opted in and supplied a retention age
15730         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15731             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15732         ELSE
15733             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15734         END IF;
15735     ELSE
15736         -- User opted in
15737         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15738     END IF;
15739
15740     IF usr_view_count.value IS NOT NULL THEN
15741         view_count := oils_json_to_text(usr_view_count.value)::INT;
15742     ELSE
15743         view_count := 1000;
15744     END IF;
15745
15746     -- show some fulfilled/canceled holds
15747     FOR h IN
15748         SELECT  *
15749           FROM  action.hold_request
15750           WHERE usr = usr_id
15751                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
15752                 AND request_time > NOW() - view_age
15753           ORDER BY request_time DESC
15754           LIMIT view_count
15755     LOOP
15756         RETURN NEXT h;
15757     END LOOP;
15758
15759     RETURN;
15760 END;
15761 $func$ LANGUAGE PLPGSQL;
15762
15763 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
15764
15765 DROP TABLE IF EXISTS serial.index_summary CASCADE;
15766
15767 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
15768
15769 DROP TABLE IF EXISTS serial.issuance CASCADE;
15770
15771 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
15772
15773 DROP TABLE IF EXISTS serial.subscription CASCADE;
15774
15775 CREATE TABLE asset.copy_template (
15776         id             SERIAL   PRIMARY KEY,
15777         owning_lib     INT      NOT NULL
15778                                 REFERENCES actor.org_unit (id)
15779                                 DEFERRABLE INITIALLY DEFERRED,
15780         creator        BIGINT   NOT NULL
15781                                 REFERENCES actor.usr (id)
15782                                 DEFERRABLE INITIALLY DEFERRED,
15783         editor         BIGINT   NOT NULL
15784                                 REFERENCES actor.usr (id)
15785                                 DEFERRABLE INITIALLY DEFERRED,
15786         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15787         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15788         name           TEXT     NOT NULL,
15789         -- columns above this point are attributes of the template itself
15790         -- columns after this point are attributes of the copy this template modifies/creates
15791         circ_lib       INT      REFERENCES actor.org_unit (id)
15792                                 DEFERRABLE INITIALLY DEFERRED,
15793         status         INT      REFERENCES config.copy_status (id)
15794                                 DEFERRABLE INITIALLY DEFERRED,
15795         location       INT      REFERENCES asset.copy_location (id)
15796                                 DEFERRABLE INITIALLY DEFERRED,
15797         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
15798                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
15799         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
15800                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
15801         age_protect    INT,
15802         circulate      BOOL,
15803         deposit        BOOL,
15804         ref            BOOL,
15805         holdable       BOOL,
15806         deposit_amount NUMERIC(6,2),
15807         price          NUMERIC(8,2),
15808         circ_modifier  TEXT,
15809         circ_as_type   TEXT,
15810         alert_message  TEXT,
15811         opac_visible   BOOL,
15812         floating       BOOL,
15813         mint_condition BOOL
15814 );
15815
15816 CREATE TABLE serial.subscription (
15817         id                     SERIAL       PRIMARY KEY,
15818         owning_lib             INT          NOT NULL DEFAULT 1
15819                                             REFERENCES actor.org_unit (id)
15820                                             ON DELETE SET NULL
15821                                             DEFERRABLE INITIALLY DEFERRED,
15822         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
15823         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
15824         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
15825                                             ON DELETE SET NULL
15826                                             DEFERRABLE INITIALLY DEFERRED,
15827         expected_date_offset   INTERVAL
15828         -- acquisitions/business-side tables link to here
15829 );
15830 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
15831 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
15832
15833 --at least one distribution per org_unit holding issues
15834 CREATE TABLE serial.distribution (
15835         id                    SERIAL  PRIMARY KEY,
15836         record_entry          BIGINT  REFERENCES serial.record_entry (id)
15837                                       ON DELETE SET NULL
15838                                       DEFERRABLE INITIALLY DEFERRED,
15839         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
15840                                           summary_method IS NULL
15841                                           OR summary_method IN ( 'add_to_sre',
15842                                           'merge_with_sre', 'use_sre_only',
15843                                           'use_sdist_only')),
15844         subscription          INT     NOT NULL
15845                                       REFERENCES serial.subscription (id)
15846                                                                   ON DELETE CASCADE
15847                                                                   DEFERRABLE INITIALLY DEFERRED,
15848         holding_lib           INT     NOT NULL
15849                                       REFERENCES actor.org_unit (id)
15850                                                                   DEFERRABLE INITIALLY DEFERRED,
15851         label                 TEXT    NOT NULL,
15852         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
15853                                       DEFERRABLE INITIALLY DEFERRED,
15854         receive_unit_template INT     REFERENCES asset.copy_template (id)
15855                                       DEFERRABLE INITIALLY DEFERRED,
15856         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
15857                                       DEFERRABLE INITIALLY DEFERRED,
15858         bind_unit_template    INT     REFERENCES asset.copy_template (id)
15859                                       DEFERRABLE INITIALLY DEFERRED,
15860         unit_label_prefix     TEXT,
15861         unit_label_suffix     TEXT
15862 );
15863 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
15864 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
15865
15866 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
15867
15868 CREATE TABLE serial.stream (
15869         id              SERIAL  PRIMARY KEY,
15870         distribution    INT     NOT NULL
15871                                 REFERENCES serial.distribution (id)
15872                                 ON DELETE CASCADE
15873                                 DEFERRABLE INITIALLY DEFERRED,
15874         routing_label   TEXT
15875 );
15876 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
15877
15878 CREATE UNIQUE INDEX label_once_per_dist
15879         ON serial.stream (distribution, routing_label)
15880         WHERE routing_label IS NOT NULL;
15881
15882 CREATE TABLE serial.routing_list_user (
15883         id             SERIAL       PRIMARY KEY,
15884         stream         INT          NOT NULL
15885                                     REFERENCES serial.stream
15886                                     ON DELETE CASCADE
15887                                     DEFERRABLE INITIALLY DEFERRED,
15888         pos            INT          NOT NULL DEFAULT 1,
15889         reader         INT          REFERENCES actor.usr
15890                                     ON DELETE CASCADE
15891                                     DEFERRABLE INITIALLY DEFERRED,
15892         department     TEXT,
15893         note           TEXT,
15894         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
15895         CONSTRAINT reader_or_dept CHECK
15896         (
15897             -- Recipient is a person or a department, but not both
15898                 (reader IS NOT NULL AND department IS NULL) OR
15899                 (reader IS NULL AND department IS NOT NULL)
15900         )
15901 );
15902 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
15903 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
15904
15905 CREATE TABLE serial.caption_and_pattern (
15906         id           SERIAL       PRIMARY KEY,
15907         subscription INT          NOT NULL REFERENCES serial.subscription (id)
15908                                   ON DELETE CASCADE
15909                                   DEFERRABLE INITIALLY DEFERRED,
15910         type         TEXT         NOT NULL
15911                                   CONSTRAINT cap_type CHECK ( type in
15912                                   ( 'basic', 'supplement', 'index' )),
15913         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
15914         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
15915         end_date     TIMESTAMP WITH TIME ZONE,
15916         active       BOOL         NOT NULL DEFAULT FALSE,
15917         pattern_code TEXT         NOT NULL,       -- must contain JSON
15918         enum_1       TEXT,
15919         enum_2       TEXT,
15920         enum_3       TEXT,
15921         enum_4       TEXT,
15922         enum_5       TEXT,
15923         enum_6       TEXT,
15924         chron_1      TEXT,
15925         chron_2      TEXT,
15926         chron_3      TEXT,
15927         chron_4      TEXT,
15928         chron_5      TEXT
15929 );
15930 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
15931
15932 CREATE TABLE serial.issuance (
15933         id              SERIAL    PRIMARY KEY,
15934         creator         INT       NOT NULL
15935                                   REFERENCES actor.usr (id)
15936                                                           DEFERRABLE INITIALLY DEFERRED,
15937         editor          INT       NOT NULL
15938                                   REFERENCES actor.usr (id)
15939                                   DEFERRABLE INITIALLY DEFERRED,
15940         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15941         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15942         subscription    INT       NOT NULL
15943                                   REFERENCES serial.subscription (id)
15944                                   ON DELETE CASCADE
15945                                   DEFERRABLE INITIALLY DEFERRED,
15946         label           TEXT,
15947         date_published  TIMESTAMP WITH TIME ZONE,
15948         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
15949                               DEFERRABLE INITIALLY DEFERRED,
15950         holding_code    TEXT,
15951         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
15952                                   (
15953                                       holding_type IS NULL
15954                                       OR holding_type IN ('basic','supplement','index')
15955                                   ),
15956         holding_link_id INT
15957         -- TODO: add columns for separate enumeration/chronology values
15958 );
15959 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
15960 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
15961 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
15962
15963 CREATE TABLE serial.unit (
15964         label           TEXT,
15965         label_sort_key  TEXT,
15966         contents        TEXT    NOT NULL
15967 ) INHERITS (asset.copy);
15968 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
15969 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
15970 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
15971 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
15972 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
15973
15974 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
15975
15976 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
15977
15978 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
15979
15980 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
15981
15982 CREATE TABLE serial.item (
15983         id              SERIAL  PRIMARY KEY,
15984         creator         INT     NOT NULL
15985                                 REFERENCES actor.usr (id)
15986                                 DEFERRABLE INITIALLY DEFERRED,
15987         editor          INT     NOT NULL
15988                                 REFERENCES actor.usr (id)
15989                                 DEFERRABLE INITIALLY DEFERRED,
15990         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15991         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
15992         issuance        INT     NOT NULL
15993                                 REFERENCES serial.issuance (id)
15994                                 ON DELETE CASCADE
15995                                 DEFERRABLE INITIALLY DEFERRED,
15996         stream          INT     NOT NULL
15997                                 REFERENCES serial.stream (id)
15998                                 ON DELETE CASCADE
15999                                 DEFERRABLE INITIALLY DEFERRED,
16000         unit            INT     REFERENCES serial.unit (id)
16001                                 ON DELETE SET NULL
16002                                 DEFERRABLE INITIALLY DEFERRED,
16003         uri             INT     REFERENCES asset.uri (id)
16004                                 ON DELETE SET NULL
16005                                 DEFERRABLE INITIALLY DEFERRED,
16006         date_expected   TIMESTAMP WITH TIME ZONE,
16007         date_received   TIMESTAMP WITH TIME ZONE,
16008         status          TEXT    CONSTRAINT valid_status CHECK (
16009                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
16010                                'Expected', 'Not Held', 'Not Published', 'Received'))
16011                             DEFAULT 'Expected',
16012         shadowed        BOOL    NOT NULL DEFAULT FALSE
16013 );
16014 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
16015 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
16016 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
16017 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
16018 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
16019 CREATE INDEX serial_item_status_idx ON serial.item (status);
16020
16021 CREATE TABLE serial.item_note (
16022         id          SERIAL  PRIMARY KEY,
16023         item        INT     NOT NULL
16024                             REFERENCES serial.item (id)
16025                             ON DELETE CASCADE
16026                             DEFERRABLE INITIALLY DEFERRED,
16027         creator     INT     NOT NULL
16028                             REFERENCES actor.usr (id)
16029                             DEFERRABLE INITIALLY DEFERRED,
16030         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
16031         pub         BOOL    NOT NULL    DEFAULT FALSE,
16032         title       TEXT    NOT NULL,
16033         value       TEXT    NOT NULL
16034 );
16035 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
16036
16037 CREATE TABLE serial.basic_summary (
16038         id                  SERIAL  PRIMARY KEY,
16039         distribution        INT     NOT NULL
16040                                     REFERENCES serial.distribution (id)
16041                                     ON DELETE CASCADE
16042                                     DEFERRABLE INITIALLY DEFERRED,
16043         generated_coverage  TEXT    NOT NULL,
16044         textual_holdings    TEXT,
16045         show_generated      BOOL    NOT NULL DEFAULT TRUE
16046 );
16047 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
16048
16049 CREATE TABLE serial.supplement_summary (
16050         id                  SERIAL  PRIMARY KEY,
16051         distribution        INT     NOT NULL
16052                                     REFERENCES serial.distribution (id)
16053                                     ON DELETE CASCADE
16054                                     DEFERRABLE INITIALLY DEFERRED,
16055         generated_coverage  TEXT    NOT NULL,
16056         textual_holdings    TEXT,
16057         show_generated      BOOL    NOT NULL DEFAULT TRUE
16058 );
16059 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
16060
16061 CREATE TABLE serial.index_summary (
16062         id                  SERIAL  PRIMARY KEY,
16063         distribution        INT     NOT NULL
16064                                     REFERENCES serial.distribution (id)
16065                                     ON DELETE CASCADE
16066                                     DEFERRABLE INITIALLY DEFERRED,
16067         generated_coverage  TEXT    NOT NULL,
16068         textual_holdings    TEXT,
16069         show_generated      BOOL    NOT NULL DEFAULT TRUE
16070 );
16071 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
16072
16073 -- 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.
16074
16075 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
16076 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16077
16078 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
16079 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;
16080
16081 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
16082 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16083
16084 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
16085 RETURNS INTEGER AS $$
16086 BEGIN
16087         RETURN EXTRACT( EPOCH FROM interval_val );
16088 END;
16089 $$ LANGUAGE plpgsql;
16090
16091 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
16092 RETURNS INTEGER AS $$
16093 BEGIN
16094         RETURN config.interval_to_seconds( interval_string::INTERVAL );
16095 END;
16096 $$ LANGUAGE plpgsql;
16097
16098 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
16099     'temp',
16100     oils_i18n_gettext(
16101         'temp',
16102         'Temporary bucket which gets deleted after use.',
16103         'cbrebt',
16104         'label'
16105     )
16106 );
16107
16108 -- 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.
16109
16110 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
16111 BEGIN
16112
16113     IF xml_is_well_formed(NEW.marc) THEN
16114         RETURN NEW;
16115     ELSE
16116         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16117     END IF;
16118     
16119 END;
16120 $func$ LANGUAGE PLPGSQL;
16121
16122 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();
16123
16124 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();
16125
16126 ALTER TABLE serial.record_entry
16127         ALTER COLUMN marc DROP NOT NULL;
16128
16129 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16130 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16131 <xsl:stylesheet
16132     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16133     xmlns:marc="http://www.loc.gov/MARC21/slim"
16134     version="1.0">
16135 <!--
16136 Copyright (C) 2010  Equinox Software, Inc.
16137 Galen Charlton <gmc@esilibrary.cOM.
16138
16139 This program is free software; you can redistribute it and/or
16140 modify it under the terms of the GNU General Public License
16141 as published by the Free Software Foundation; either version 2
16142 of the License, or (at your option) any later version.
16143
16144 This program is distributed in the hope that it will be useful,
16145 but WITHOUT ANY WARRANTY; without even the implied warranty of
16146 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16147 GNU General Public License for more details.
16148
16149 marc21_expand_880.xsl - stylesheet used during indexing to
16150                         map alternative graphical representations
16151                         of MARC fields stored in 880 fields
16152                         to the corresponding tag name and value.
16153
16154 For example, if a MARC record for a Chinese book has
16155
16156 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16157 880.00 $6 245-01/$1 $a八十三年短篇小說選
16158
16159 this stylesheet will transform it to the equivalent of
16160
16161 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16162 245.00 $6 245-01/$1 $a八十三年短篇小說選
16163
16164 -->
16165     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16166
16167     <xsl:template match="@*|node()">
16168         <xsl:copy>
16169             <xsl:apply-templates select="@*|node()"/>
16170         </xsl:copy>
16171     </xsl:template>
16172
16173     <xsl:template match="//marc:datafield[@tag='880']">
16174         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16175             <marc:datafield>
16176                 <xsl:attribute name="tag">
16177                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16178                 </xsl:attribute>
16179                 <xsl:attribute name="ind1">
16180                     <xsl:value-of select="@ind1" />
16181                 </xsl:attribute>
16182                 <xsl:attribute name="ind2">
16183                     <xsl:value-of select="@ind2" />
16184                 </xsl:attribute>
16185                 <xsl:apply-templates />
16186             </marc:datafield>
16187         </xsl:if>
16188     </xsl:template>
16189     
16190 </xsl:stylesheet>$$);
16191
16192 -- Splitting the ingest trigger up into little bits
16193
16194 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16195     flag INTEGER PRIMARY KEY
16196 ) ON COMMIT DROP;
16197 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16198
16199 -- cause failure if either of the tables we want to drop have rows
16200 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16201 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16202
16203 DROP TABLE IF EXISTS asset.copy_transparency_map;
16204 DROP TABLE IF EXISTS asset.copy_transparency;
16205
16206 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16207
16208 -- We won't necessarily use all of these, but they are here for completeness.
16209 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16210 -- Values are the EDI code value + 1000
16211
16212 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16213 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16214 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16215 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16216 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16217 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16218 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16219 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16220 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16221 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16222 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16223 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16224 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16225 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16226 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16227 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16228 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16229 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16230 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16231 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16232 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16233 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16234 ('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).'),
16235 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16236 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16237 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16238 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16239 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16240 ('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.'),
16241 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16242 ('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.'),
16243 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16244 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16245 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16246 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16247 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16248 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16249 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16250 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16251 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16252 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16253 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16254 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16255 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16256 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16257 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16258 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16259 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16260 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16261 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16262 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16263 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16264 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16265 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16266 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16267 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16268 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16269 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16270 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16271 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16272 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16273 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16274 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16275 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16276 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16277 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16278 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16279 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16280 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16281 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16282 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16283 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16284 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16285 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16286 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16287 ('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).'),
16288 ('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).'),
16289 ('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).'),
16290 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16291 ('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).'),
16292 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16293 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16294 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16295 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16296 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16297 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16298 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16299 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16300 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16301 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16302 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16303 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16304 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16305 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16306 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16307 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16308 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16309 ('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.'),
16310 ('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.'),
16311 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16312 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16313 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16314 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16315 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16316 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16317 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16318 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16319 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16320 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16321 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16322 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16323 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16324 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16325 ('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.'),
16326 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16327 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16328
16329 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16330     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16331  
16332 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16333  
16334 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16335         'Remove Parenthesized Substring',
16336         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16337         'remove_paren_substring',
16338         0
16339 );
16340
16341 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16342         'Trim Surrounding Space',
16343         'Trim leading and trailing spaces from extracted text.',
16344         'btrim',
16345         0
16346 );
16347
16348 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16349     SELECT  m.id,
16350             i.id,
16351             -2
16352       FROM  config.metabib_field m,
16353             config.index_normalizer i
16354       WHERE i.func IN ('remove_paren_substring')
16355             AND m.id IN (26);
16356
16357 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16358     SELECT  m.id,
16359             i.id,
16360             -1
16361       FROM  config.metabib_field m,
16362             config.index_normalizer i
16363       WHERE i.func IN ('btrim')
16364             AND m.id IN (26);
16365
16366 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16367 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16368 DECLARE
16369     dyn_profile     vandelay.compile_profile%ROWTYPE;
16370     replace_rule    TEXT;
16371     tmp_marc        TEXT;
16372     trgt_marc        TEXT;
16373     tmpl_marc        TEXT;
16374     match_count     INT;
16375 BEGIN
16376
16377     IF target_marc IS NULL OR template_marc IS NULL THEN
16378         -- RAISE NOTICE 'no marc for target or template record';
16379         RETURN NULL;
16380     END IF;
16381
16382     dyn_profile := vandelay.compile_profile( template_marc );
16383
16384     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16385         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16386         RETURN NULL;
16387     END IF;
16388
16389     IF dyn_profile.replace_rule <> '' THEN
16390         trgt_marc = target_marc;
16391         tmpl_marc = template_marc;
16392         replace_rule = dyn_profile.replace_rule;
16393     ELSE
16394         tmp_marc = target_marc;
16395         trgt_marc = template_marc;
16396         tmpl_marc = tmp_marc;
16397         replace_rule = dyn_profile.preserve_rule;
16398     END IF;
16399
16400     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16401
16402 END;
16403 $$ LANGUAGE PLPGSQL;
16404
16405 -- Function to generate an ephemeral overlay template from an authority record
16406 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16407
16408     use MARC::Record;
16409     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16410
16411     my $xml = shift;
16412     my $r = MARC::Record->new_from_xml( $xml );
16413
16414     return undef unless ($r);
16415
16416     my $id = shift() || $r->subfield( '901' => 'c' );
16417     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16418     return undef unless ($id); # We need an ID!
16419
16420     my $tmpl = MARC::Record->new();
16421
16422     my @rule_fields;
16423     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16424
16425         my $tag = $field->tag;
16426         my $i1 = $field->indicator(1);
16427         my $i2 = $field->indicator(2);
16428         my $sf = join '', map { $_->[0] } $field->subfields;
16429         my @data = map { @$_ } $field->subfields;
16430
16431         my @replace_them;
16432
16433         # Map the authority field to bib fields it can control.
16434         if ($tag >= 100 and $tag <= 111) {       # names
16435             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16436         } elsif ($tag eq '130') {                # uniform title
16437             @replace_them = qw/130 240 440 730 830/;
16438         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16439             @replace_them = ($tag + 500);
16440         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16441             @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/;
16442         } else {
16443             next;
16444         }
16445
16446         # Dummy up the bib-side data
16447         $tmpl->append_fields(
16448             map {
16449                 MARC::Field->new( $_, $i1, $i2, @data )
16450             } @replace_them
16451         );
16452
16453         # Construct some 'replace' rules
16454         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16455     }
16456
16457     # Insert the replace rules into the template
16458     $tmpl->append_fields(
16459         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16460     );
16461
16462     $xml = $tmpl->as_xml_record;
16463     $xml =~ s/^<\?.+?\?>$//mo;
16464     $xml =~ s/\n//sgo;
16465     $xml =~ s/>\s+</></sgo;
16466
16467     return $xml;
16468
16469 $func$ LANGUAGE PLPERLU;
16470
16471 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16472     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16473 $func$ LANGUAGE SQL;
16474
16475 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16476     SELECT authority.generate_overlay_template( $1, NULL );
16477 $func$ LANGUAGE SQL;
16478
16479 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16480 DELETE FROM config.metabib_field WHERE id = 26;
16481
16482 -- Making this a global_flag (UI accessible) instead of an internal_flag
16483 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16484     VALUES (
16485         'ingest.disable_authority_linking',
16486         oils_i18n_gettext(
16487             'ingest.disable_authority_linking',
16488             'Authority Automation: Disable bib-authority link tracking',
16489             'cgf', 
16490             'label'
16491         )
16492     );
16493 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16494 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16495
16496 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16497     VALUES (
16498         'ingest.disable_authority_auto_update',
16499         oils_i18n_gettext(
16500             'ingest.disable_authority_auto_update',
16501             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16502             'cgf', 
16503             'label'
16504         )
16505     );
16506
16507 -- Enable automated ingest of authority records; just insert the row into
16508 -- authority.record_entry and authority.full_rec will automatically be populated
16509
16510 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16511     UPDATE  biblio.record_entry
16512       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16513       WHERE id = $2;
16514     SELECT $1;
16515 $func$ LANGUAGE SQL;
16516
16517 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16518     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16519 $func$ LANGUAGE SQL;
16520
16521 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16522
16523 use MARC::Record;
16524 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16525
16526 my $xml = shift;
16527 my $r = MARC::Record->new_from_xml( $xml );
16528
16529 return_next( { tag => 'LDR', value => $r->leader } );
16530
16531 for my $f ( $r->fields ) {
16532     if ($f->is_control_field) {
16533         return_next({ tag => $f->tag, value => $f->data });
16534     } else {
16535         for my $s ($f->subfields) {
16536             return_next({
16537                 tag      => $f->tag,
16538                 ind1     => $f->indicator(1),
16539                 ind2     => $f->indicator(2),
16540                 subfield => $s->[0],
16541                 value    => $s->[1]
16542             });
16543
16544         }
16545     }
16546 }
16547
16548 return undef;
16549
16550 $func$ LANGUAGE PLPERLU;
16551
16552 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16553 DECLARE
16554     auth    authority.record_entry%ROWTYPE;
16555     output    authority.full_rec%ROWTYPE;
16556     field    RECORD;
16557 BEGIN
16558     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16559
16560     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16561         output.record := rid;
16562         output.ind1 := field.ind1;
16563         output.ind2 := field.ind2;
16564         output.tag := field.tag;
16565         output.subfield := field.subfield;
16566         IF field.subfield IS NOT NULL THEN
16567             output.value := naco_normalize(field.value, field.subfield);
16568         ELSE
16569             output.value := field.value;
16570         END IF;
16571
16572         CONTINUE WHEN output.value IS NULL;
16573
16574         RETURN NEXT output;
16575     END LOOP;
16576 END;
16577 $func$ LANGUAGE PLPGSQL;
16578
16579 -- authority.rec_descriptor appears to be unused currently
16580 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16581 BEGIN
16582     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16583 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16584 --        SELECT  auth_id, ;
16585
16586     RETURN;
16587 END;
16588 $func$ LANGUAGE PLPGSQL;
16589
16590 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16591 BEGIN
16592     DELETE FROM authority.full_rec WHERE record = auth_id;
16593     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16594         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16595
16596     RETURN;
16597 END;
16598 $func$ LANGUAGE PLPGSQL;
16599
16600 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16601 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16602 BEGIN
16603
16604     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16605         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16606         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
16607           -- Should remove matching $0 from controlled fields at the same time?
16608         RETURN NEW; -- and we're done
16609     END IF;
16610
16611     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16612         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16613
16614         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16615             RETURN NEW;
16616         END IF;
16617     END IF;
16618
16619     -- Flatten and insert the afr data
16620     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16621     IF NOT FOUND THEN
16622         PERFORM authority.reingest_authority_full_rec(NEW.id);
16623 -- authority.rec_descriptor is not currently used
16624 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16625 --        IF NOT FOUND THEN
16626 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16627 --        END IF;
16628     END IF;
16629
16630     RETURN NEW;
16631 END;
16632 $func$ LANGUAGE PLPGSQL;
16633
16634 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 ();
16635
16636 -- Some records manage to get XML namespace declarations into each element,
16637 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16638 -- This broke the old maintain_901(), so we'll make the regex more robust
16639
16640 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16641 BEGIN
16642     -- Remove any existing 901 fields before we insert the authoritative one
16643     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16644     IF TG_TABLE_SCHEMA = 'biblio' THEN
16645         NEW.marc := REGEXP_REPLACE(
16646             NEW.marc,
16647             E'(</(?:[^:]*?:)?record>)',
16648             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16649                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16650                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16651                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16652                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16653                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16654                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16655              E'</datafield>\\1'
16656         );
16657     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16658         NEW.marc := REGEXP_REPLACE(
16659             NEW.marc,
16660             E'(</(?:[^:]*?:)?record>)',
16661             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16662                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16663                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16664              E'</datafield>\\1'
16665         );
16666     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16667         NEW.marc := REGEXP_REPLACE(
16668             NEW.marc,
16669             E'(</(?:[^:]*?:)?record>)',
16670             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16671                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16672                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16673                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16674                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16675              E'</datafield>\\1'
16676         );
16677     ELSE
16678         NEW.marc := REGEXP_REPLACE(
16679             NEW.marc,
16680             E'(</(?:[^:]*?:)?record>)',
16681             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16682                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16683                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16684              E'</datafield>\\1'
16685         );
16686     END IF;
16687
16688     RETURN NEW;
16689 END;
16690 $func$ LANGUAGE PLPGSQL;
16691
16692 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16693 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16694 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16695  
16696 -- In booking, elbow room defines:
16697 --  a) how far in the future you must make a reservation on a given item if
16698 --      that item will have to transit somewhere to fulfill the reservation.
16699 --  b) how soon a reservation must be starting for the reserved item to
16700 --      be op-captured by the checkin interface.
16701
16702 -- We don't want to clobber any default_elbow room at any level:
16703
16704 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16705 DECLARE
16706     existing    actor.org_unit_setting%ROWTYPE;
16707 BEGIN
16708     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16709     IF NOT FOUND THEN
16710         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16711             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16712             'circ.booking_reservation.default_elbow_room',
16713             '"1 day"'
16714         );
16715         RETURN 1;
16716     END IF;
16717     RETURN 0;
16718 END;
16719 $$ LANGUAGE plpgsql;
16720
16721 SELECT pg_temp.default_elbow();
16722
16723 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16724
16725 -- returns the distinct set of target copy IDs from a user's visible circulation history
16726 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16727     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16728 $$ LANGUAGE SQL;
16729
16730 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
16731 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
16732 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
16733 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
16734
16735 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
16736
16737 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
16738 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
16739
16740 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16741     VALUES (
16742         'cat.maintain_control_numbers',
16743         oils_i18n_gettext(
16744             'cat.maintain_control_numbers',
16745             'Cat: Maintain 001/003/035 according to the MARC21 specification',
16746             'cgf', 
16747             'label'
16748         )
16749     );
16750
16751 INSERT INTO config.global_flag (name, label, enabled)
16752     VALUES (
16753         'circ.holds.empty_issuance_ok',
16754         oils_i18n_gettext(
16755             'circ.holds.empty_issuance_ok',
16756             'Holds: Allow holds on empty issuances',
16757             'cgf',
16758             'label'
16759         ),
16760         TRUE
16761     );
16762
16763 INSERT INTO config.global_flag (name, label, enabled)
16764     VALUES (
16765         'circ.holds.usr_not_requestor',
16766         oils_i18n_gettext(
16767             'circ.holds.usr_not_requestor',
16768             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
16769             'cgf',
16770             'label'
16771         ),
16772         TRUE
16773     );
16774
16775 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
16776 use strict;
16777 use MARC::Record;
16778 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16779 use Encode;
16780 use Unicode::Normalize;
16781
16782 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
16783 my $schema = $_TD->{table_schema};
16784 my $rec_id = $_TD->{new}{id};
16785
16786 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
16787 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
16788 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
16789     return;
16790 }
16791
16792 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
16793 my $ou_cni = 'EVRGRN';
16794
16795 my $owner;
16796 if ($schema eq 'serial') {
16797     $owner = $_TD->{new}{owning_lib};
16798 } else {
16799     # are.owner and bre.owner can be null, so fall back to the consortial setting
16800     $owner = $_TD->{new}{owner} || 1;
16801 }
16802
16803 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
16804 if ($ous_rv->{processed}) {
16805     $ou_cni = $ous_rv->{rows}[0]->{value};
16806     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
16807 } else {
16808     # Fall back to the shortname of the OU if there was no OU setting
16809     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
16810     if ($ous_rv->{processed}) {
16811         $ou_cni = $ous_rv->{rows}[0]->{shortname};
16812     }
16813 }
16814
16815 my ($create, $munge) = (0, 0);
16816 my ($orig_001, $orig_003) = ('', '');
16817
16818 # Incoming MARC records may have multiple 001s or 003s, despite the spec
16819 my @control_ids = $record->field('003');
16820 my @scns = $record->field('035');
16821
16822 foreach my $id_field ('001', '003') {
16823     my $spec_value;
16824     my @controls = $record->field($id_field);
16825
16826     if ($id_field eq '001') {
16827         $spec_value = $rec_id;
16828     } else {
16829         $spec_value = $ou_cni;
16830     }
16831
16832     # Create the 001/003 if none exist
16833     if (scalar(@controls) == 0) {
16834         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
16835         $create = 1;
16836     } elsif (scalar(@controls) > 1) {
16837         # Do we already have the right 001/003 value in the existing set?
16838         unless (grep $_->data() eq $spec_value, @controls) {
16839             $munge = 1;
16840         }
16841
16842         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
16843         foreach my $control (@controls) {
16844             unless ($control->data() eq $spec_value) {
16845                 $record->delete_field($control);
16846             }
16847         }
16848     } else {
16849         # Only one field; check to see if we need to munge it
16850         unless (grep $_->data() eq $spec_value, @controls) {
16851             $munge = 1;
16852         }
16853     }
16854 }
16855
16856 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
16857 if ($munge) {
16858     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
16859
16860     # Do not create duplicate 035 fields
16861     unless (grep $_->subfield('a') eq $scn, @scns) {
16862         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
16863     }
16864 }
16865
16866 # Set the 001/003 and update the MARC
16867 if ($create or $munge) {
16868     $record->field('001')->data($rec_id);
16869     $record->field('003')->data($ou_cni);
16870
16871     my $xml = $record->as_xml_record();
16872     $xml =~ s/\n//sgo;
16873     $xml =~ s/^<\?xml.+\?\s*>//go;
16874     $xml =~ s/>\s+</></go;
16875     $xml =~ s/\p{Cc}//go;
16876
16877     # Embed a version of OpenILS::Application::AppUtils->entityize()
16878     # to avoid having to set PERL5LIB for PostgreSQL as well
16879
16880     # If we are going to convert non-ASCII characters to XML entities,
16881     # we had better be dealing with a UTF8 string to begin with
16882     $xml = decode_utf8($xml);
16883
16884     $xml = NFC($xml);
16885
16886     # Convert raw ampersands to entities
16887     $xml =~ s/&(?!\S+;)/&amp;/gso;
16888
16889     # Convert Unicode characters to entities
16890     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
16891
16892     $xml =~ s/[\x00-\x1f]//go;
16893     $_TD->{new}{marc} = $xml;
16894
16895     return "MODIFY";
16896 }
16897
16898 return;
16899 $func$ LANGUAGE PLPERLU;
16900
16901 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16902 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16903 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
16904
16905 INSERT INTO metabib.facet_entry (source, field, value)
16906     SELECT source, field, value FROM (
16907         SELECT * FROM metabib.author_field_entry
16908             UNION ALL
16909         SELECT * FROM metabib.keyword_field_entry
16910             UNION ALL
16911         SELECT * FROM metabib.identifier_field_entry
16912             UNION ALL
16913         SELECT * FROM metabib.title_field_entry
16914             UNION ALL
16915         SELECT * FROM metabib.subject_field_entry
16916             UNION ALL
16917         SELECT * FROM metabib.series_field_entry
16918         )x
16919     WHERE x.index_vector = '';
16920         
16921 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
16922 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
16923 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
16924 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
16925 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
16926 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
16927
16928 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
16929 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
16930 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
16931
16932 -- copy OPAC visibility materialized view
16933 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
16934
16935     TRUNCATE TABLE asset.opac_visible_copies;
16936
16937     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16938     SELECT  cp.id, cp.circ_lib, cn.record
16939     FROM  asset.copy cp
16940         JOIN asset.call_number cn ON (cn.id = cp.call_number)
16941         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
16942         JOIN asset.copy_location cl ON (cp.location = cl.id)
16943         JOIN config.copy_status cs ON (cp.status = cs.id)
16944         JOIN biblio.record_entry b ON (cn.record = b.id)
16945     WHERE NOT cp.deleted
16946         AND NOT cn.deleted
16947         AND NOT b.deleted
16948         AND cs.opac_visible
16949         AND cl.opac_visible
16950         AND cp.opac_visible
16951         AND a.opac_visible;
16952
16953 $$ LANGUAGE SQL;
16954 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
16955 Rebuild the copy OPAC visibility cache.  Useful during migrations.
16956 $$;
16957
16958 -- and actually populate the table
16959 SELECT asset.refresh_opac_visible_copies_mat_view();
16960
16961 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
16962 DECLARE
16963     add_query       TEXT;
16964     remove_query    TEXT;
16965     do_add          BOOLEAN := false;
16966     do_remove       BOOLEAN := false;
16967 BEGIN
16968     add_query := $$
16969             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
16970                 SELECT  cp.id, cp.circ_lib, cn.record
16971                   FROM  asset.copy cp
16972                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
16973                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
16974                         JOIN asset.copy_location cl ON (cp.location = cl.id)
16975                         JOIN config.copy_status cs ON (cp.status = cs.id)
16976                         JOIN biblio.record_entry b ON (cn.record = b.id)
16977                   WHERE NOT cp.deleted
16978                         AND NOT cn.deleted
16979                         AND NOT b.deleted
16980                         AND cs.opac_visible
16981                         AND cl.opac_visible
16982                         AND cp.opac_visible
16983                         AND a.opac_visible
16984     $$;
16985  
16986     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
16987
16988     IF TG_OP = 'INSERT' THEN
16989
16990         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
16991             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
16992             EXECUTE add_query;
16993         END IF;
16994
16995         RETURN NEW;
16996
16997     END IF;
16998
16999     -- handle items first, since with circulation activity
17000     -- their statuses change frequently
17001     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17002
17003         IF OLD.location    <> NEW.location OR
17004            OLD.call_number <> NEW.call_number OR
17005            OLD.status      <> NEW.status OR
17006            OLD.circ_lib    <> NEW.circ_lib THEN
17007             -- any of these could change visibility, but
17008             -- we'll save some queries and not try to calculate
17009             -- the change directly
17010             do_remove := true;
17011             do_add := true;
17012         ELSE
17013
17014             IF OLD.deleted <> NEW.deleted THEN
17015                 IF NEW.deleted THEN
17016                     do_remove := true;
17017                 ELSE
17018                     do_add := true;
17019                 END IF;
17020             END IF;
17021
17022             IF OLD.opac_visible <> NEW.opac_visible THEN
17023                 IF OLD.opac_visible THEN
17024                     do_remove := true;
17025                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
17026                                         -- is also marked opac_visible
17027                     do_add := true;
17028                 END IF;
17029             END IF;
17030
17031         END IF;
17032
17033         IF do_remove THEN
17034             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
17035         END IF;
17036         IF do_add THEN
17037             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17038             EXECUTE add_query;
17039         END IF;
17040
17041         RETURN NEW;
17042
17043     END IF;
17044
17045     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
17046  
17047         IF OLD.deleted AND NEW.deleted THEN -- do nothing
17048
17049             RETURN NEW;
17050  
17051         ELSIF NEW.deleted THEN -- remove rows
17052  
17053             IF TG_TABLE_NAME = 'call_number' THEN
17054                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
17055             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17056                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
17057             END IF;
17058  
17059             RETURN NEW;
17060  
17061         ELSIF OLD.deleted THEN -- add rows
17062  
17063             IF TG_TABLE_NAME IN ('copy','unit') THEN
17064                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17065             ELSIF TG_TABLE_NAME = 'call_number' THEN
17066                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17067             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17068                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
17069             END IF;
17070  
17071             EXECUTE add_query;
17072             RETURN NEW;
17073  
17074         END IF;
17075  
17076     END IF;
17077
17078     IF TG_TABLE_NAME = 'call_number' THEN
17079
17080         IF OLD.record <> NEW.record THEN
17081             -- call number is linked to different bib
17082             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
17083             EXECUTE remove_query;
17084             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17085             EXECUTE add_query;
17086         END IF;
17087
17088         RETURN NEW;
17089
17090     END IF;
17091
17092     IF TG_TABLE_NAME IN ('record_entry') THEN
17093         RETURN NEW; -- don't have 'opac_visible'
17094     END IF;
17095
17096     -- actor.org_unit, asset.copy_location, asset.copy_status
17097     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
17098
17099         RETURN NEW;
17100
17101     ELSIF NEW.opac_visible THEN -- add rows
17102
17103         IF TG_TABLE_NAME = 'org_unit' THEN
17104             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
17105         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17106             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
17107         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17108             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
17109         END IF;
17110  
17111         EXECUTE add_query;
17112  
17113     ELSE -- delete rows
17114
17115         IF TG_TABLE_NAME = 'org_unit' THEN
17116             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17117         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17118             remove_query := remove_query || 'location = ' || NEW.id || ');';
17119         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17120             remove_query := remove_query || 'status = ' || NEW.id || ');';
17121         END IF;
17122  
17123         EXECUTE remove_query;
17124  
17125     END IF;
17126  
17127     RETURN NEW;
17128 END;
17129 $func$ LANGUAGE PLPGSQL;
17130 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17131 Trigger function to update the copy OPAC visiblity cache.
17132 $$;
17133 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();
17134 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17135 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();
17136 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();
17137 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17138 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();
17139 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();
17140
17141 -- must create this rule explicitly; it is not inherited from asset.copy
17142 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;
17143
17144 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);
17145
17146 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17147 DECLARE
17148     moved_objects INT := 0;
17149     bib_id        INT := 0;
17150     bib_rec       biblio.record_entry%ROWTYPE;
17151     auth_link     authority.bib_linking%ROWTYPE;
17152 BEGIN
17153
17154     -- 1. Make source_record MARC a copy of the target_record to get auto-sync in linked bib records
17155     UPDATE authority.record_entry
17156       SET marc = (
17157         SELECT marc
17158           FROM authority.record_entry
17159           WHERE id = target_record
17160       )
17161       WHERE id = source_record;
17162
17163     -- 2. Update all bib records with the ID from target_record in their $0
17164     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre 
17165       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17166       WHERE abl.authority = target_record LOOP
17167
17168         UPDATE biblio.record_entry
17169           SET marc = REGEXP_REPLACE(marc, 
17170             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17171             E'\\1' || target_record || '<', 'g')
17172           WHERE id = bib_rec.id;
17173
17174           moved_objects := moved_objects + 1;
17175     END LOOP;
17176
17177     -- 3. "Delete" source_record
17178     DELETE FROM authority.record_entry
17179       WHERE id = source_record;
17180
17181     RETURN moved_objects;
17182 END;
17183 $func$ LANGUAGE plpgsql;
17184
17185 -- serial.record_entry already had an owner column spelled "owning_lib"
17186 -- Adjust the table and affected functions accordingly
17187
17188 ALTER TABLE serial.record_entry DROP COLUMN owner;
17189
17190 CREATE TABLE actor.usr_saved_search (
17191     id              SERIAL          PRIMARY KEY,
17192         owner           INT             NOT NULL REFERENCES actor.usr (id)
17193                                         ON DELETE CASCADE
17194                                         DEFERRABLE INITIALLY DEFERRED,
17195         name            TEXT            NOT NULL,
17196         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17197         query_text      TEXT            NOT NULL,
17198         query_type      TEXT            NOT NULL
17199                                         CONSTRAINT valid_query_text CHECK (
17200                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17201                                         -- we may add other types someday
17202         target          TEXT            NOT NULL
17203                                         CONSTRAINT valid_target CHECK (
17204                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17205         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17206 );
17207
17208 -- Apply Dan Wells' changes to the serial schema, from the
17209 -- seials-integration branch
17210
17211 CREATE TABLE serial.subscription_note (
17212         id           SERIAL PRIMARY KEY,
17213         subscription INT    NOT NULL
17214                             REFERENCES serial.subscription (id)
17215                             ON DELETE CASCADE
17216                             DEFERRABLE INITIALLY DEFERRED,
17217         creator      INT    NOT NULL
17218                             REFERENCES actor.usr (id)
17219                             DEFERRABLE INITIALLY DEFERRED,
17220         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17221         pub          BOOL   NOT NULL DEFAULT FALSE,
17222         title        TEXT   NOT NULL,
17223         value        TEXT   NOT NULL
17224 );
17225 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17226
17227 CREATE TABLE serial.distribution_note (
17228         id           SERIAL PRIMARY KEY,
17229         distribution INT    NOT NULL
17230                             REFERENCES serial.distribution (id)
17231                             ON DELETE CASCADE
17232                             DEFERRABLE INITIALLY DEFERRED,
17233         creator      INT    NOT NULL
17234                             REFERENCES actor.usr (id)
17235                             DEFERRABLE INITIALLY DEFERRED,
17236         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17237         pub          BOOL   NOT NULL DEFAULT FALSE,
17238         title        TEXT   NOT NULL,
17239         value        TEXT   NOT NULL
17240 );
17241 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17242
17243 ------- Begin surgery on serial.unit
17244
17245 ALTER TABLE serial.unit
17246         DROP COLUMN label;
17247
17248 ALTER TABLE serial.unit
17249         RENAME COLUMN label_sort_key TO sort_key;
17250
17251 ALTER TABLE serial.unit
17252         RENAME COLUMN contents TO detailed_contents;
17253
17254 ALTER TABLE serial.unit
17255         ADD COLUMN summary_contents TEXT;
17256
17257 UPDATE serial.unit
17258 SET summary_contents = detailed_contents;
17259
17260 ALTER TABLE serial.unit
17261         ALTER column summary_contents SET NOT NULL;
17262
17263 ------- End surgery on serial.unit
17264
17265 -- 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' );
17266
17267 -- Now rebuild the constraints dropped via cascade.
17268 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17269 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17270 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17271
17272 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17273
17274 DELETE FROM config.metabib_field_index_norm_map
17275     WHERE norm IN (
17276         SELECT id 
17277             FROM config.index_normalizer
17278             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17279     )
17280     AND field = 18
17281 ;
17282
17283 -- We won't necessarily use all of these, but they are here for completeness.
17284 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17285 -- Values are the EDI code value + 1200
17286
17287 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17288 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17289 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17290 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17291 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17292 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17293 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17294 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17295 (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.'),
17296 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17297 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17298 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17299 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17300 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17301 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17302 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17303 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17304 (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.'),
17305 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17306 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17307 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17308 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17309 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17310 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17311 (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.'),
17312 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17313 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17314 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17315 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17316 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17317 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17318 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17319 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17320 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17321 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17322 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17323 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17324 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17325 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17326 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17327 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17328 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17329 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17330 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17331 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17332 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17333 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17334 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17335 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17336 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17337 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17338 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17339 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17340 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17341 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17342 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17343 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17344 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17345 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17346 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17347 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17348 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17349 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17350 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17351 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17352 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17353 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17354 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17355 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17356 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17357 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17358 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17359 (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.'),
17360 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17361 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17362 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17363 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17364 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17365 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17366 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17367 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17368 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17369 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17370 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17371 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17372 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17373 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17374 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17375 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17376 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17377 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17378 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17379 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17380 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17381 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17382 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17383 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17384 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17385 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17386 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17387 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17388 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17389 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17390 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17391 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17392 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17393 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17394 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17395 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17396 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17397 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17398 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17399 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17400 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17401 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17402 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17403 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17404 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17405 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17406 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17407 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17408 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17409 (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.'),
17410 (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.'),
17411 (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.'),
17412 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17413 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17414 (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.'),
17415 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17416 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17417 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17418 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17419 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17420 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17421 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17422 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17423 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17424 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17425 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17426 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17427 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17428 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17429 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17430 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17431 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17432 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17433 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17434 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17435 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17436 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17437 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17438 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17439 (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.'),
17440 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17441 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17442 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17443 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17444 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17445 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17446 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17447 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17448 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17449 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17450 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17451 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17452 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17453 (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.'),
17454 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17455 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17456 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17457 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17458 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17459 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17460 (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.'),
17461 (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.'),
17462 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17463 (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.'),
17464 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17465 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17466 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17467 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17468 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17469 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17470 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17471 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17472 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17473 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17474 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17475 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17476 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17477 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17478 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17479 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17480 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17481 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17482 (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.'),
17483 (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.'),
17484 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17485 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17486 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17487 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17488 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17489 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17490 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17491 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17492 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17493 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17494 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17495 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17496 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17497 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17498 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17499 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17500 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17501 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17502 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17503 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17504 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17505 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17506 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17507 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17508 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17509 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17510 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17511 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17512 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17513 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17514 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17515 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17516 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17517 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17518 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17519 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17520 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17521 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17522 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17523 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17524 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17525 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17526 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17527 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17528 (1, 't', 1442, 'Number of months', 'The number of months.'),
17529 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17530 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17531 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17532 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17533 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17534 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17535 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17536 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17537 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17538 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17539 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17540 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17541 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17542 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17543 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17544 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17545 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17546 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17547 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17548 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17549 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17550 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17551 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17552 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17553 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17554 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17555 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17556 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17557 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17558 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17559 (1, 't', 1473, 'Agents', 'The number of agents.'),
17560 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17561 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17562 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17563 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17564 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17565 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17566 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17567 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17568 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17569 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17570 (1, 't', 1484, 'Departments', 'The number of departments.'),
17571 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17572 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17573 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17574 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17575 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17576 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17577 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17578 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17579 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17580 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17581 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17582 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17583 (1, 't', 1497, 'Executives', 'The number of executives.'),
17584 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17585 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17586 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17587 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17588 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17589 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17590 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17591 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17592 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17593 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17594 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17595 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17596 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17597 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17598 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17599 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17600 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17601 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17602 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17603 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17604 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17605 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17606 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17607 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17608 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17609 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17610 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17611 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17612 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17613 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17614 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17615 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17616 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17617 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17618 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17619 (1, 't', 1533, 'Seats',        'The number of seats.'),
17620 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17621 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17622 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17623 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17624 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17625 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17626 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17627 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17628 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17629 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17630 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17631 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17632 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17633 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17634 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17635 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17636 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17637 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17638 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17639 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17640 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17641 (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.'),
17642 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17643 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17644 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17645 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17646 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17647 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17648 (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.'),
17649 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17650 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17651 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17652 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17653 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17654 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17655 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17656 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17657 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17658 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17659 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17660 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17661 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17662 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17663 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17664 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17665 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17666 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17667 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17668 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17669 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17670 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17671 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17672 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17673 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17674 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17675 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17676 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17677 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17678 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17679 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17680 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17681 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17682 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17683 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17684 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17685 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17686 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17687 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17688 (1, 't', 1602, 'Patients',         'Number of patients.'),
17689 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17690 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17691 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17692 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17693 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17694 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17695 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17696 (1, 't', 1610, 'Operators',        'Number of operators.'),
17697 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17698 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17699 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17700 (1, 't', 1614, 'Machines',         'Number of machines.'),
17701 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17702 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17703 (1, 't', 1617, 'Directors',        'Number of directors.'),
17704 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17705 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17706 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17707 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17708 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17709 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17710 (1, 't', 1624, 'Beds', 'Number of beds.'),
17711 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17712 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17713 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17714 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
17715 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
17716 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
17717 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
17718 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
17719 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
17720 (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.'),
17721 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
17722 (1, 't', 1636, 'Professor', 'The number of professors.'),
17723 (1, 't', 1637, 'Seller',    'The number of sellers.'),
17724 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
17725 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
17726 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
17727 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
17728 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
17729 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
17730 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
17731 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
17732 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
17733 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
17734 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
17735 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
17736 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
17737 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
17738 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
17739 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
17740 (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.'),
17741 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
17742 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
17743 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
17744 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
17745 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
17746 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
17747 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
17748 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
17749 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
17750 (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.'),
17751 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
17752 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
17753 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
17754 (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.'),
17755 (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.'),
17756 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
17757 (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.'),
17758 (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.'),
17759 (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.'),
17760 (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.'),
17761 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
17762 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
17763 (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.'),
17764 (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.'),
17765 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
17766 (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.'),
17767 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
17768 (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.'),
17769 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
17770 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
17771 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
17772 (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).'),
17773 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
17774 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
17775 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
17776 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
17777 ;
17778 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
17779
17780 CREATE TABLE acq.serial_claim (
17781     id     SERIAL           PRIMARY KEY,
17782     type   INT              NOT NULL REFERENCES acq.claim_type
17783                                      DEFERRABLE INITIALLY DEFERRED,
17784     item    BIGINT          NOT NULL REFERENCES serial.item
17785                                      DEFERRABLE INITIALLY DEFERRED
17786 );
17787
17788 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
17789
17790 CREATE TABLE acq.serial_claim_event (
17791     id             BIGSERIAL        PRIMARY KEY,
17792     type           INT              NOT NULL REFERENCES acq.claim_event_type
17793                                              DEFERRABLE INITIALLY DEFERRED,
17794     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
17795                                              DEFERRABLE INITIALLY DEFERRED,
17796     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
17797     creator        INT              NOT NULL REFERENCES actor.usr
17798                                              DEFERRABLE INITIALLY DEFERRED,
17799     note           TEXT
17800 );
17801
17802 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
17803
17804 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
17805
17806 -- now what about the auditor.*_lifecycle views??
17807
17808 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17809     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
17810 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17811     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
17812 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
17813 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
17814
17815 CREATE TABLE asset.call_number_class (
17816     id             bigserial     PRIMARY KEY,
17817     name           TEXT          NOT NULL,
17818     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
17819     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17820 );
17821
17822 COMMENT ON TABLE asset.call_number_class IS $$
17823 Defines the call number normalization database functions in the "normalizer"
17824 column and the tag/subfield combinations to use to lookup the call number in
17825 the "field" column for a given classification scheme. Tag/subfield combinations
17826 are delimited by commas.
17827 $$;
17828
17829 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
17830     ('Generic', 'asset.label_normalizer_generic'),
17831     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
17832     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
17833 ;
17834
17835 -- Generic fields
17836 UPDATE asset.call_number_class
17837     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17838     WHERE id = 1
17839 ;
17840
17841 -- Dewey fields
17842 UPDATE asset.call_number_class
17843     SET field = '080ab,082ab'
17844     WHERE id = 2
17845 ;
17846
17847 -- LC fields
17848 UPDATE asset.call_number_class
17849     SET field = '050ab,055ab'
17850     WHERE id = 3
17851 ;
17852  
17853 ALTER TABLE asset.call_number
17854         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
17855                 REFERENCES asset.call_number_class(id)
17856                 DEFERRABLE INITIALLY DEFERRED;
17857
17858 ALTER TABLE asset.call_number
17859         ADD COLUMN label_sortkey TEXT;
17860
17861 CREATE INDEX asset_call_number_label_sortkey
17862         ON asset.call_number(oils_text_as_bytea(label_sortkey));
17863
17864 ALTER TABLE auditor.asset_call_number_history
17865         ADD COLUMN label_class BIGINT;
17866
17867 ALTER TABLE auditor.asset_call_number_history
17868         ADD COLUMN label_sortkey TEXT;
17869
17870 -- Pick up the new columns in dependent views
17871
17872 DROP VIEW auditor.asset_call_number_lifecycle;
17873
17874 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17875
17876 DROP VIEW auditor.asset_call_number_lifecycle;
17877
17878 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
17879
17880 DROP VIEW IF EXISTS stats.fleshed_call_number;
17881
17882 CREATE VIEW stats.fleshed_call_number AS
17883         SELECT  cn.*,
17884             CAST(cn.create_date AS DATE) AS create_date_day,
17885         CAST(cn.edit_date AS DATE) AS edit_date_day,
17886         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
17887         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
17888             rd.item_lang,
17889                 rd.item_type,
17890                 rd.item_form
17891         FROM    asset.call_number cn
17892                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
17893
17894 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
17895 DECLARE
17896     sortkey        TEXT := '';
17897 BEGIN
17898     sortkey := NEW.label_sortkey;
17899
17900     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
17901        quote_literal( NEW.label ) || ')'
17902        FROM asset.call_number_class acnc
17903        WHERE acnc.id = NEW.label_class
17904        INTO sortkey;
17905
17906     NEW.label_sortkey = sortkey;
17907
17908     RETURN NEW;
17909 END;
17910 $func$ LANGUAGE PLPGSQL;
17911
17912 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
17913     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
17914     # thus could probably be considered a derived work, although nothing was
17915     # directly copied - but to err on the safe side of providing attribution:
17916     # Copyright (C) 2007 LibLime
17917     # Licensed under the GPL v2 or later
17918
17919     use strict;
17920     use warnings;
17921
17922     # Converts the callnumber to uppercase
17923     # Strips spaces from start and end of the call number
17924     # Converts anything other than letters, digits, and periods into underscores
17925     # Collapses multiple underscores into a single underscore
17926     my $callnum = uc(shift);
17927     $callnum =~ s/^\s//g;
17928     $callnum =~ s/\s$//g;
17929     $callnum =~ s/[^A-Z0-9_.]/_/g;
17930     $callnum =~ s/_{2,}/_/g;
17931
17932     return $callnum;
17933 $func$ LANGUAGE PLPERLU;
17934
17935 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
17936     # Derived from the Koha C4::ClassSortRoutine::Dewey module
17937     # Copyright (C) 2007 LibLime
17938     # Licensed under the GPL v2 or later
17939
17940     use strict;
17941     use warnings;
17942
17943     my $init = uc(shift);
17944     $init =~ s/^\s+//;
17945     $init =~ s/\s+$//;
17946     $init =~ s!/!!g;
17947     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
17948     my @tokens = split /\.|\s+/, $init;
17949     my $digit_group_count = 0;
17950     for (my $i = 0; $i <= $#tokens; $i++) {
17951         if ($tokens[$i] =~ /^\d+$/) {
17952             $digit_group_count++;
17953             if (2 == $digit_group_count) {
17954                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
17955                 $tokens[$i] =~ tr/ /0/;
17956             }
17957         }
17958     }
17959     my $key = join("_", @tokens);
17960     $key =~ s/[^\p{IsAlnum}_]//g;
17961
17962     return $key;
17963
17964 $func$ LANGUAGE PLPERLU;
17965
17966 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
17967     use strict;
17968     use warnings;
17969
17970     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
17971     # The author hopes to upload it to CPAN some day, which would make our lives easier
17972     use Library::CallNumber::LC;
17973
17974     my $callnum = Library::CallNumber::LC->new(shift);
17975     return $callnum->normalize();
17976
17977 $func$ LANGUAGE PLPERLU;
17978
17979 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$
17980 DECLARE
17981     ans RECORD;
17982     trans INT;
17983 BEGIN
17984     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;
17985
17986     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
17987         RETURN QUERY
17988         SELECT  ans.depth,
17989                 ans.id,
17990                 COUNT( av.id ),
17991                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
17992                 COUNT( av.id ),
17993                 trans
17994           FROM
17995                 actor.org_unit_descendants(ans.id) d
17996                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
17997                 JOIN asset.copy cp ON (cp.id = av.id)
17998           GROUP BY 1,2,6;
17999
18000         IF NOT FOUND THEN
18001             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18002         END IF;
18003
18004     END LOOP;
18005
18006     RETURN;
18007 END;
18008 $f$ LANGUAGE PLPGSQL;
18009
18010 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$
18011 DECLARE
18012     ans RECORD;
18013     trans INT;
18014 BEGIN
18015     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;
18016
18017     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18018         RETURN QUERY
18019         SELECT  -1,
18020                 ans.id,
18021                 COUNT( av.id ),
18022                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18023                 COUNT( av.id ),
18024                 trans
18025           FROM
18026                 actor.org_unit_descendants(ans.id) d
18027                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18028                 JOIN asset.copy cp ON (cp.id = av.id)
18029           GROUP BY 1,2,6;
18030
18031         IF NOT FOUND THEN
18032             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18033         END IF;
18034
18035     END LOOP;
18036
18037     RETURN;
18038 END;
18039 $f$ LANGUAGE PLPGSQL;
18040
18041 CREATE OR REPLACE FUNCTION asset.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$
18042 DECLARE
18043     ans RECORD;
18044     trans INT;
18045 BEGIN
18046     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
18047
18048     FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
18049         RETURN QUERY
18050         SELECT  ans.depth,
18051                 ans.id,
18052                 COUNT( cp.id ),
18053                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18054                 COUNT( cp.id ),
18055                 trans
18056           FROM
18057                 actor.org_unit_descendants(ans.id) d
18058                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18059                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18060           GROUP BY 1,2,6;
18061
18062         IF NOT FOUND THEN
18063             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18064         END IF;
18065
18066     END LOOP;
18067
18068     RETURN;
18069 END;
18070 $f$ LANGUAGE PLPGSQL;
18071
18072 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$
18073 DECLARE
18074     ans RECORD;
18075     trans INT;
18076 BEGIN
18077     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;
18078
18079     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18080         RETURN QUERY
18081         SELECT  -1,
18082                 ans.id,
18083                 COUNT( cp.id ),
18084                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18085                 COUNT( cp.id ),
18086                 trans
18087           FROM
18088                 actor.org_unit_descendants(ans.id) d
18089                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18090                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18091           GROUP BY 1,2,6;
18092
18093         IF NOT FOUND THEN
18094             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18095         END IF;
18096
18097     END LOOP;
18098
18099     RETURN;
18100 END;
18101 $f$ LANGUAGE PLPGSQL;
18102
18103 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$
18104 BEGIN
18105     IF staff IS TRUE THEN
18106         IF place > 0 THEN
18107             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
18108         ELSE
18109             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
18110         END IF;
18111     ELSE
18112         IF place > 0 THEN
18113             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18114         ELSE
18115             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18116         END IF;
18117     END IF;
18118
18119     RETURN;
18120 END;
18121 $f$ LANGUAGE PLPGSQL;
18122
18123 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$
18124 DECLARE
18125     ans RECORD;
18126     trans INT;
18127 BEGIN
18128     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;
18129
18130     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
18131         RETURN QUERY
18132         SELECT  ans.depth,
18133                 ans.id,
18134                 COUNT( av.id ),
18135                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18136                 COUNT( av.id ),
18137                 trans
18138           FROM
18139                 actor.org_unit_descendants(ans.id) d
18140                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18141                 JOIN asset.copy cp ON (cp.id = av.id)
18142                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18143           GROUP BY 1,2,6;
18144
18145         IF NOT FOUND THEN
18146             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18147         END IF;
18148
18149     END LOOP;
18150
18151     RETURN;
18152 END;
18153 $f$ LANGUAGE PLPGSQL;
18154
18155 CREATE OR REPLACE FUNCTION asset.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$
18156 DECLARE
18157     ans RECORD;
18158     trans INT;
18159 BEGIN
18160     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;
18161
18162     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18163         RETURN QUERY
18164         SELECT  -1,
18165                 ans.id,
18166                 COUNT( av.id ),
18167                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18168                 COUNT( av.id ),
18169                 trans
18170           FROM
18171                 actor.org_unit_descendants(ans.id) d
18172                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18173                 JOIN asset.copy cp ON (cp.id = av.id)
18174                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18175           GROUP BY 1,2,6;
18176
18177         IF NOT FOUND THEN
18178             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18179         END IF;
18180
18181     END LOOP;
18182
18183     RETURN;
18184 END;
18185 $f$ LANGUAGE PLPGSQL;
18186
18187 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$
18188 DECLARE
18189     ans RECORD;
18190     trans INT;
18191 BEGIN
18192     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;
18193
18194     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
18195         RETURN QUERY
18196         SELECT  ans.depth,
18197                 ans.id,
18198                 COUNT( cp.id ),
18199                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18200                 COUNT( cp.id ),
18201                 trans
18202           FROM
18203                 actor.org_unit_descendants(ans.id) d
18204                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18205                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18206                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18207           GROUP BY 1,2,6;
18208
18209         IF NOT FOUND THEN
18210             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18211         END IF;
18212
18213     END LOOP;
18214
18215     RETURN;
18216 END;
18217 $f$ LANGUAGE PLPGSQL;
18218
18219 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$
18220 DECLARE
18221     ans RECORD;
18222     trans INT;
18223 BEGIN
18224     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;
18225
18226     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18227         RETURN QUERY
18228         SELECT  -1,
18229                 ans.id,
18230                 COUNT( cp.id ),
18231                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18232                 COUNT( cp.id ),
18233                 trans
18234           FROM
18235                 actor.org_unit_descendants(ans.id) d
18236                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18237                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18238                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18239           GROUP BY 1,2,6;
18240
18241         IF NOT FOUND THEN
18242             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18243         END IF;
18244
18245     END LOOP;
18246
18247     RETURN;
18248 END;
18249 $f$ LANGUAGE PLPGSQL;
18250
18251 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$
18252 BEGIN
18253     IF staff IS TRUE THEN
18254         IF place > 0 THEN
18255             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18256         ELSE
18257             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18258         END IF;
18259     ELSE
18260         IF place > 0 THEN
18261             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18262         ELSE
18263             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18264         END IF;
18265     END IF;
18266
18267     RETURN;
18268 END;
18269 $f$ LANGUAGE PLPGSQL;
18270
18271 -- No transaction is required
18272
18273 -- Triggers on the vandelay.queued_*_record tables delete entries from
18274 -- the associated vandelay.queued_*_record_attr tables based on the record's
18275 -- ID; create an index on that column to avoid sequential scans for each
18276 -- queued record that is deleted
18277 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18278 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18279
18280 -- Avoid sequential scans for queue retrieval operations by providing an
18281 -- index on the queue column
18282 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18283 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18284
18285 -- Start picking up call number label prefixes and suffixes
18286 -- from asset.copy_location
18287 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18288 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18289
18290 DROP VIEW auditor.asset_copy_lifecycle;
18291
18292 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18293
18294 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18295
18296 -- Let's not break existing reports
18297 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18298 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18299
18300 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18301 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18302 SELECT  r.id,
18303     r.fingerprint,
18304     r.quality,
18305     r.tcn_source,
18306     r.tcn_value,
18307     FIRST(title.value) AS title,
18308     FIRST(author.value) AS author,
18309     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18310     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18311     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18312     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18313   FROM  biblio.record_entry r
18314     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18315     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18316     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18317     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18318     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18319     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18320   GROUP BY 1,2,3,4,5;
18321
18322 -- Correct the ISSN array definition for reporter.simple_record
18323
18324 CREATE OR REPLACE VIEW reporter.simple_record AS
18325 SELECT  r.id,
18326         s.metarecord,
18327         r.fingerprint,
18328         r.quality,
18329         r.tcn_source,
18330         r.tcn_value,
18331         title.value AS title,
18332         uniform_title.value AS uniform_title,
18333         author.value AS author,
18334         publisher.value AS publisher,
18335         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18336         series_title.value AS series_title,
18337         series_statement.value AS series_statement,
18338         summary.value AS summary,
18339         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18340         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18341         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18342         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18343         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18344         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18345         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18346         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
18347   FROM  biblio.record_entry r
18348         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18349         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18350         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18351         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18352         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18353         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18354         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18355         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18356         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')
18357         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18358         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18359   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18360
18361 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18362     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
18363 $$ LANGUAGE SQL;
18364
18365 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18366 BEGIN
18367     IF TG_OP = 'DELETE' THEN
18368         PERFORM reporter.simple_rec_delete(NEW.id);
18369     ELSE
18370         PERFORM reporter.simple_rec_update(NEW.id);
18371     END IF;
18372
18373     RETURN NEW;
18374 END;
18375 $func$ LANGUAGE PLPGSQL;
18376
18377 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
18378
18379 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18380
18381 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18382
18383 UPDATE config.org_unit_setting_type
18384     SET view_perm = (SELECT id FROM permission.perm_list
18385         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18386     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18387
18388 UPDATE config.org_unit_setting_type
18389     SET update_perm = (SELECT id FROM permission.perm_list
18390         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18391     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18392
18393 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18394     VALUES (
18395         'opac.fully_compressed_serial_holdings',
18396         'OPAC: Use fully compressed serial holdings',
18397         'Show fully compressed serial holdings for all libraries at and below
18398         the current context unit',
18399         'bool'
18400     );
18401
18402 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18403     use strict;
18404     use warnings;
18405
18406     use utf8;
18407     use MARC::Record;
18408     use MARC::File::XML (BinaryEncoding => 'UTF8');
18409     use UUID::Tiny ':std';
18410
18411     my $xml = shift() or return undef;
18412
18413     my $r;
18414
18415     # Prevent errors in XML parsing from blowing out ungracefully
18416     eval {
18417         $r = MARC::Record->new_from_xml( $xml );
18418         1;
18419     } or do {
18420        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18421     };
18422
18423     if (!$r) {
18424        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18425     }
18426
18427     # From http://www.loc.gov/standards/sourcelist/subject.html
18428     my $thes_code_map = {
18429         a => 'lcsh',
18430         b => 'lcshac',
18431         c => 'mesh',
18432         d => 'nal',
18433         k => 'cash',
18434         n => 'notapplicable',
18435         r => 'aat',
18436         s => 'sears',
18437         v => 'rvm',
18438     };
18439
18440     # Default to "No attempt to code" if the leader is horribly broken
18441     my $fixed_field = $r->field('008');
18442     my $thes_char = '|';
18443     if ($fixed_field) {
18444         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18445     }
18446
18447     my $thes_code = 'UNDEFINED';
18448
18449     if ($thes_char eq 'z') {
18450         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18451         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18452     } elsif ($thes_code_map->{$thes_char}) {
18453         $thes_code = $thes_code_map->{$thes_char};
18454     }
18455
18456     my $auth_txt = '';
18457     my $head = $r->field('1..');
18458     if ($head) {
18459         # Concatenate all of these subfields together, prefixed by their code
18460         # to prevent collisions along the lines of "Fiction, North Carolina"
18461         foreach my $sf ($head->subfields()) {
18462             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18463         }
18464     }
18465
18466     # Perhaps better to parameterize the spi and pass as a parameter
18467     $auth_txt =~ s/'//go;
18468
18469     if ($auth_txt) {
18470         my $result = spi_exec_query("SELECT public.naco_normalize('$auth_txt') AS norm_text");
18471         my $norm_txt = $result->{rows}[0]->{norm_text};
18472         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18473     }
18474
18475     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18476 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18477
18478 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18479 /**
18480 * Extract the authority heading, thesaurus, and NACO-normalized values
18481 * from an authority record. The primary purpose is to build a unique
18482 * index to defend against duplicated authority records from the same
18483 * thesaurus.
18484 */
18485 $$;
18486
18487 DROP INDEX authority.authority_record_unique_tcn;
18488 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18489 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18490
18491 ALTER TABLE acq.provider_contact
18492         ALTER COLUMN name SET NOT NULL;
18493
18494 ALTER TABLE actor.stat_cat
18495         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18496
18497 -- Recreate some foreign keys that were somehow dropped, probably
18498 -- by some kind of cascade from an inherited table:
18499
18500 ALTER TABLE action.reservation_transit_copy
18501         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
18502                 REFERENCES booking.resource(id)
18503                 ON DELETE CASCADE
18504                 DEFERRABLE INITIALLY DEFERRED,
18505         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
18506                 REFERENCES booking.reservation(id)
18507                 ON DELETE SET NULL
18508                 DEFERRABLE INITIALLY DEFERRED;
18509
18510 CREATE INDEX user_bucket_item_target_user_idx
18511         ON container.user_bucket_item ( target_user );
18512
18513 CREATE INDEX m_c_t_collector_idx
18514         ON money.collections_tracker ( collector );
18515
18516 CREATE INDEX aud_actor_usr_address_hist_id_idx
18517         ON auditor.actor_usr_address_history ( id );
18518
18519 CREATE INDEX aud_actor_usr_hist_id_idx
18520         ON auditor.actor_usr_history ( id );
18521
18522 CREATE INDEX aud_asset_cn_hist_creator_idx
18523         ON auditor.asset_call_number_history ( creator );
18524
18525 CREATE INDEX aud_asset_cn_hist_editor_idx
18526         ON auditor.asset_call_number_history ( editor );
18527
18528 CREATE INDEX aud_asset_cp_hist_creator_idx
18529         ON auditor.asset_copy_history ( creator );
18530
18531 CREATE INDEX aud_asset_cp_hist_editor_idx
18532         ON auditor.asset_copy_history ( editor );
18533
18534 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18535         ON auditor.biblio_record_entry_history ( creator );
18536
18537 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18538         ON auditor.biblio_record_entry_history ( editor );
18539
18540 CREATE TABLE action.hold_request_note (
18541
18542     id     BIGSERIAL PRIMARY KEY,
18543     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18544                               ON DELETE CASCADE
18545                               DEFERRABLE INITIALLY DEFERRED,
18546     title  TEXT      NOT NULL,
18547     body   TEXT      NOT NULL,
18548     slip   BOOL      NOT NULL DEFAULT FALSE,
18549     pub    BOOL      NOT NULL DEFAULT FALSE,
18550     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18551
18552 );
18553 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18554
18555 -- Tweak a constraint to add a CASCADE
18556
18557 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18558
18559 ALTER TABLE action.hold_notification
18560         ADD CONSTRAINT hold_notification_hold_fkey
18561                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18562                 ON DELETE CASCADE
18563                 DEFERRABLE INITIALLY DEFERRED;
18564
18565 CREATE TRIGGER asset_label_sortkey_trigger
18566     BEFORE UPDATE OR INSERT ON asset.call_number
18567     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18568
18569 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18570 RETURNS VOID AS $$
18571 --
18572 -- Delete expired circulation bucket items for all users that have
18573 -- a setting for patron.max_reading_list_interval.
18574 --
18575 DECLARE
18576     today        TIMESTAMP WITH TIME ZONE;
18577     threshold    TIMESTAMP WITH TIME ZONE;
18578         usr_setting  RECORD;
18579 BEGIN
18580         SELECT date_trunc( 'day', now() ) INTO today;
18581         --
18582         FOR usr_setting in
18583                 SELECT
18584                         usr,
18585                         value
18586                 FROM
18587                         actor.usr_setting
18588                 WHERE
18589                         name = 'patron.max_reading_list_interval'
18590         LOOP
18591                 --
18592                 -- Make sure the setting is a valid interval
18593                 --
18594                 BEGIN
18595                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18596                 EXCEPTION
18597                         WHEN OTHERS THEN
18598                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18599                                         usr_setting.usr, usr_setting.value;
18600                                 CONTINUE;
18601                 END;
18602                 --
18603                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18604                 --
18605         DELETE FROM container.copy_bucket_item
18606         WHERE
18607                 bucket IN
18608                 (
18609                     SELECT
18610                         id
18611                     FROM
18612                         container.copy_bucket
18613                     WHERE
18614                         owner = usr_setting.usr
18615                         AND btype = 'circ_history'
18616                 )
18617                 AND create_time < threshold;
18618         END LOOP;
18619         --
18620 END;
18621 $$ LANGUAGE plpgsql;
18622
18623 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18624 /*
18625  * Delete expired circulation bucket items for all users that have
18626  * a setting for patron.max_reading_list_interval.
18627 */
18628 $$;
18629
18630 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18631          ac_usr IN INTEGER
18632 ) RETURNS VOID AS $$
18633 --
18634 -- Delete old circulation bucket items for a specified user.
18635 -- "Old" means older than the interval specified by a
18636 -- user-level setting, if it is so specified.
18637 --
18638 DECLARE
18639     threshold TIMESTAMP WITH TIME ZONE;
18640 BEGIN
18641         -- Sanity check
18642         IF ac_usr IS NULL THEN
18643                 RETURN;
18644         END IF;
18645         -- Determine the threshold date that defines "old".  Subtract the
18646         -- interval from the system date, then truncate to midnight.
18647         SELECT
18648                 date_trunc( 
18649                         'day',
18650                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18651                 )
18652         INTO
18653                 threshold
18654         FROM
18655                 actor.usr_setting
18656         WHERE
18657                 usr = ac_usr
18658                 AND name = 'patron.max_reading_list_interval';
18659         --
18660         IF threshold is null THEN
18661                 -- No interval defined; don't delete anything
18662                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18663                 return;
18664         END IF;
18665         --
18666         -- RAISE NOTICE 'Date threshold: %', threshold;
18667         --
18668         -- Threshold found; do the delete
18669         delete from container.copy_bucket_item
18670         where
18671                 bucket in
18672                 (
18673                         select
18674                                 id
18675                         from
18676                                 container.copy_bucket
18677                         where
18678                                 owner = ac_usr
18679                                 and btype = 'circ_history'
18680                 )
18681                 and create_time < threshold;
18682         --
18683         RETURN;
18684 END;
18685 $$ LANGUAGE plpgsql;
18686
18687 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18688 /*
18689  * Delete old circulation bucket items for a specified user.
18690  * "Old" means older than the interval specified by a
18691  * user-level setting, if it is so specified.
18692 */
18693 $$;
18694
18695 CREATE OR REPLACE VIEW reporter.hold_request_record AS
18696 SELECT  id,
18697     target,
18698     hold_type,
18699     CASE
18700         WHEN hold_type = 'T'
18701             THEN target
18702         WHEN hold_type = 'I'
18703             THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
18704         WHEN hold_type = 'V'
18705             THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
18706         WHEN hold_type IN ('C','R','F')
18707             THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
18708         WHEN hold_type = 'M'
18709             THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
18710     END AS bib_record
18711   FROM  action.hold_request ahr;
18712
18713 UPDATE  metabib.rec_descriptor
18714   SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
18715         date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
18716
18717 -- Change some ints to bigints:
18718
18719 ALTER TABLE container.biblio_record_entry_bucket_item
18720         ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
18721
18722 ALTER TABLE vandelay.queued_bib_record
18723         ALTER COLUMN imported_as SET DATA TYPE bigint;
18724
18725 ALTER TABLE action.hold_copy_map
18726         ALTER COLUMN id SET DATA TYPE bigint;
18727
18728 -- Make due times get pushed to 23:59:59 on insert OR update
18729 DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
18730 CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
18731
18732 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove )
18733 SELECT 'upc', 'UPC', '//*[@tag="024" and @ind1="1"]/*[@code="a"]', $r$(?:-|\s.+$)$r$
18734 WHERE NOT EXISTS (
18735     SELECT 1 FROM acq.lineitem_marc_attr_definition WHERE code = 'upc'
18736 );
18737
18738 COMMIT;
18739
18740 -- Some operations go outside of the transaction, because they may
18741 -- legitimately fail.
18742
18743 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
18744 \qecho doesn't exist; ignore those errors if they occur.
18745
18746 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
18747
18748 ALTER TABLE auditor.action_hold_request_history
18749 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
18750
18751 ALTER TABLE auditor.action_hold_request_history
18752 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
18753
18754 \qecho Outside of the transaction: adding indexes that may or may not exist.
18755 \qecho If any of these CREATE INDEX statements fails because the index already
18756 \qecho exists, ignore the failure.
18757
18758 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
18759 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
18760 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
18761 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
18762 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
18763 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
18764 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
18765 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
18766 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
18767 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
18768 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
18769 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
18770 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
18771 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
18772 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
18773 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
18774 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
18775 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
18776 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
18777 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
18778 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
18779 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
18780 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
18781 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
18782 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
18783 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
18784 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
18785 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
18786 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
18787 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
18788 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
18789 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
18790 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
18791
18792 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
18793
18794 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
18795
18796 \qecho If the following CREATE INDEX fails, It will be necessary to do some
18797 \qecho data cleanup as described in the comments.
18798
18799 CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
18800     ON authority.record_entry (authority.normalize_heading(marc))
18801         WHERE deleted IS FALSE or deleted = FALSE;
18802
18803 -- If the unique index fails, uncomment the following to create
18804 -- a regular index that will help find the duplicates in a hurry:
18805 --CREATE INDEX by_heading_and_thesaurus
18806 --    ON authority.record_entry (authority.normalize_heading(marc))
18807 --    WHERE deleted IS FALSE or deleted = FALSE
18808 --;
18809
18810 -- Then find the duplicates like so to get an idea of how much
18811 -- pain you're looking at to clean things up:
18812 --SELECT id, authority.normalize_heading(marc)
18813 --    FROM authority.record_entry
18814 --    WHERE authority.normalize_heading(marc) IN (
18815 --        SELECT authority.normalize_heading(marc)
18816 --        FROM authority.record_entry
18817 --        GROUP BY authority.normalize_heading(marc)
18818 --        HAVING COUNT(*) > 1
18819 --    )
18820 --;
18821
18822 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
18823 -- statement succeeds, drop the temporary index to avoid unnecessary
18824 -- duplication:
18825 -- DROP INDEX authority.by_heading_and_thesaurus;
18826
18827 -- 0448.data.trigger.circ.staff_age_to_lost.sql
18828
18829 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
18830     (   'circ.staff_age_to_lost',
18831         'circ', 
18832         oils_i18n_gettext(
18833             'circ.staff_age_to_lost',
18834             'An overdue circulation should be aged to a Lost status.',
18835             'ath',
18836             'description'
18837         ), 
18838         TRUE
18839     )
18840 ;
18841
18842 INSERT INTO action_trigger.event_definition (
18843         id,
18844         active,
18845         owner,
18846         name,
18847         hook,
18848         validator,
18849         reactor,
18850         delay_field
18851     ) VALUES (
18852         36,
18853         FALSE,
18854         1,
18855         'circ.staff_age_to_lost',
18856         'circ.staff_age_to_lost',
18857         'CircIsOverdue',
18858         'MarkItemLost',
18859         'due_date'
18860     )
18861 ;
18862
18863
18864 \qecho Upgrade script completed.
18865
18866